1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
use std::{any::TypeId, marker::PhantomData};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
/// A marker for when the type of template state is unknown.
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub struct UnknownStateType;
/// A wrapper for template state stored as a [`Value`]. This loses the
/// underlying type information, but allows for serialization. This is a
/// necessary compromise, since, without types being first-class citizens in
/// Rust, full template type management appears presently impossible.
#[derive(Clone, Debug)]
pub struct TemplateStateWithType<T: Serialize + DeserializeOwned> {
/// The underlying state, stored as a [`Value`].
pub(crate) state: Value,
/// The user's actual type.
ty: PhantomData<T>,
}
impl<T: Serialize + DeserializeOwned + 'static> TemplateStateWithType<T> {
/// Convert the template state into its underlying concrete type, when that
/// type is known.
pub(crate) fn into_concrete(self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.state)
}
/// Creates a new empty template state.
pub fn empty() -> Self {
Self {
state: Value::Null,
ty: PhantomData,
}
}
/// Checks if this state is empty. This not only checks for states created
/// as `Value::Null`, but also those created with `()` explicitly set as
/// their underlying type.
pub fn is_empty(&self) -> bool {
self.state.is_null() || TypeId::of::<T>() == TypeId::of::<()>()
}
/// Creates a new template state by deserializing from a string.
pub(crate) fn from_str(s: &str) -> Result<Self, serde_json::Error> {
let state = Self {
state: serde_json::from_str(s)?,
ty: PhantomData,
};
Ok(state)
}
/// Creates a new template state from a pre-deserialized [`Value`].
///
/// Note that end users should almost always prefer `::from_str()`, and this
/// is intended primarily for server integrations.
pub fn from_value(v: Value) -> Self {
Self {
state: v,
ty: PhantomData,
}
}
/// Transform this into a [`TemplateStateWithType`] with a different type.
/// Once this is done, `.to_concrete()` can be used to get this type out
/// of the container.
pub(crate) fn change_type<U: Serialize + DeserializeOwned>(self) -> TemplateStateWithType<U> {
TemplateStateWithType {
state: self.state,
ty: PhantomData,
}
}
}
// Any user state should be able to be made into this with a simple `.into()`
// for ergonomics
impl<T: Serialize + DeserializeOwned> From<T> for TemplateState {
fn from(state: T) -> Self {
Self {
// This will happen at Perseus build-time (and should never happen unless the user uses non-string map types)
state: serde_json::to_value(state).expect("serializing template state failed (this is almost certainly due to non-string map keys in your types, which can't be serialized by serde)"),
ty: PhantomData,
}
}
}
/// A type alias for template state that has been converted into a [`Value`]
/// without retaining the information of the original type, which is done
/// internally to eliminate the need for generics, which cannot be used
/// internally in Perseus for user state. The actual type is restored at the
/// last minute when it's needed.
pub type TemplateState = TemplateStateWithType<UnknownStateType>;