use crate::errors::Error; use crate::models::proto::cofd::*; use crate::models::users::User; use crate::schema::characters; use diesel_derive_enum::DbEnum; use prost::bytes::BytesMut; use serde_derive::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 } } } #[derive(DbEnum, Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString)] pub enum CharacterDataType { ChroniclesOfDarknessV1, ChangelingV1, } impl CharacterDataType { pub fn create_data(&self) -> Result { use prost::Message; use CharacterDataType::*; let data: BytesMut = match self { ChroniclesOfDarknessV1 => { let sheet = CofdSheet::default(); let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet)); sheet.encode(&mut buf)?; buf } ChangelingV1 => { let mut sheet = ChangelingSheet::default(); sheet.base = Some(CofdSheet::default()); let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet)); sheet.encode(&mut buf)?; buf } }; Ok(data) } } /// An entry that appears in a user's character list. Properties are /// in order of table columns. #[derive(Serialize, Debug, Queryable)] 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 { ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::()?), ChangelingV1 => 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) } } #[derive(Serialize, Debug, Queryable)] 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. #[derive(Insertable)] #[table_name = "characters"] pub struct NewCharacter { pub user_id: i32, pub viewable: bool, pub character_name: String, pub data_type: CharacterDataType, pub data_version: i32, pub data: Vec, }