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>;