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