180 lines
5.6 KiB
Rust
180 lines
5.6 KiB
Rust
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<Self>
|
|
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<BytesMut, Error> {
|
|
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, Identifiable, AsChangeset)]
|
|
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<u8>,
|
|
}
|
|
|
|
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<T>(&self) -> Result<T, Error>
|
|
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<Box<DynCharacterData>, Error> {
|
|
use CharacterDataType::*;
|
|
let decoded: Box<dyn erased_serde::Serialize> = match self.data_type {
|
|
ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::<CofdSheet>()?),
|
|
ChangelingV1 => Box::new(self.try_deserialize::<ChangelingSheet>()?),
|
|
};
|
|
|
|
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<Character, Error> {
|
|
// 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. Consumes the data.
|
|
pub fn update_data<T>(&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, 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<u8>,
|
|
}
|