use crate::errors::Error; use crate::models::proto::cofd::*; use crate::models::users::User; use prost::bytes::BytesMut; use serde::Serialize; use strum::{EnumIter, EnumString}; /// Dynamic character data is an opaque container type that holds /// successfully deserialized character data protobuf object of any /// type. It does not know what kind of type it has. This is a /// semantically more appropriate name for what is returned from the /// dyn_deserialize function. pub(crate) type DynCharacterData = dyn erased_serde::Serialize; /// Control system visibility of a character for a particular user. /// Implemented as a trait because there are multiple character /// structs that need this. pub(crate) trait Visibility { /// User ID that owns this character. fn user_id(&self) -> i32; /// If the character is publicly visible. fn viewable(&self) -> bool; /// Transform to an Option that holds the character, if the /// character is viewable to a potentially existing user. A /// character is "visible" if the public viewable property is set /// to true, or the user is the owner of the character. Consumes /// self. fn as_visible_for(self, user: Option<&User>) -> Option where Self: std::marker::Sized, { if self.viewable() || user.map(|u| u.id) == Some(self.user_id()) { Some(self) } else { None } } } /// Enum representing all game systems supported by the character /// service. Game systems are kept unique instead of lumping them /// together under common umbrella systems, even if the different /// games use the same (or similar) character sheets. This is because /// of the possibility for slight differences in rules and data /// between even similar systems. It's simpler to err on the side of /// uniqueness. Usually, systems based on the same ruleset will have /// one character sheet type, with a oneof field for game-specific /// information. #[derive(Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString, sqlx::Type)] #[sqlx(rename_all = "snake_case")] pub enum CharacterDataType { /// A character for the core Chronicles of Darkness rules. ChroniclesOfDarkness, /// A character for Changeling 2E rules. Changeling, } impl CharacterDataType { /// Create the default serialized protobuf data (character sheet) /// for the game system represented by the enum variant. pub fn default_serialized_data(&self) -> Result { use prost::Message; use CharacterDataType::*; let data: BytesMut = match self { ChroniclesOfDarkness | Changeling => { let sheet = CofdSheet::default_sheet(*self); let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet)); sheet.encode(&mut buf)?; buf } }; Ok(data) } /// Returns whether or not this enum variant represents a Chronicles /// of Darkness game system. pub fn is_cofd_system(&self) -> bool { use CharacterDataType::*; match self { ChroniclesOfDarkness | Changeling => true, } } } /// An entry that appears in a user's character list. Properties are /// in order of table columns. #[derive(Serialize, Debug, sqlx::FromRow)] pub struct Character { pub id: i32, pub user_id: i32, pub viewable: bool, pub character_name: String, pub data_type: CharacterDataType, pub data_version: i32, pub data: Vec, } impl Visibility for Character { fn user_id(&self) -> i32 { self.user_id } fn viewable(&self) -> bool { self.viewable } } impl Character { /// Attempt to deserialize the character's data into the given /// type, which must be one of the protobuf types. pub fn try_deserialize(&self) -> Result where T: prost::Message + std::default::Default, { let decoded = T::decode(self.data.as_ref())?; Ok(decoded) } /// Attempt to deserialize the character's data based on its /// stored type, but return the deserialized protobuf type as a /// trait object. Primarily used for passing character sheets to /// templates or other places (like a REST API). pub fn dyn_deserialize(&self) -> Result, Error> { use CharacterDataType::*; let decoded: Box = match self.data_type { ChroniclesOfDarkness => Box::new(self.try_deserialize::()?), Changeling => Box::new(self.try_deserialize::()?), }; Ok(decoded) } /// Upgrade the stored protobuf character data to its newest /// iteration, if new types have been created. Consumes self. pub fn uprade(self) -> Result { // Currently, this just returns itself because there are no // iterations. But we could for example go from CofdSheet v1 // to CofdSheet v2 by deserializing v1, applying a migration // to v2, then reserializing, and copying over all other // fields. Ok(self) } /// Update the existing character with new serialized protobuf /// data. pub fn update_data(&mut self, data: &T) -> Result<(), Error> where T: prost::Message + std::default::Default, { let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&data)); data.encode(&mut buf)?; self.data = buf.to_vec(); Ok(()) } } /// Same as regular character type, but without the actual protobuf /// data loaded into memory. #[derive(Serialize, Debug, sqlx::FromRow)] pub struct StrippedCharacter { pub id: i32, pub user_id: i32, pub viewable: bool, pub character_name: String, pub data_type: CharacterDataType, pub data_version: i32, } impl Visibility for StrippedCharacter { fn user_id(&self) -> i32 { self.user_id } fn viewable(&self) -> bool { self.viewable } } /// Represents insert of a new character into the database. Property /// names correspond to columns. pub struct NewCharacter<'a> { pub user_id: i32, pub viewable: bool, pub character_name: &'a str, pub data_type: CharacterDataType, pub data_version: i32, pub data: &'a [u8], }