tenebrous-sheets/src/models/characters.rs

166 lines
5.1 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)]
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)
}
}
#[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>,
}