Skip to main content

cotis_modules_debug/serialize_targets/
leaf_serialization.rs

1//! Type-level lists for deserializing type-erased leaf configs at replay time.
2//!
3//! During recording, leaf element configs (text, images, custom widgets) are serialized into
4//! [`LeafConfigData`] blobs without preserving their Rust type. At replay, the
5//! [`LeafConfigList`] chain tells the interpreter which concrete types to try, in order.
6//!
7//! # Building a leaf type list
8//!
9//! Start with [`LeafConfigListNil`] (the empty list) and extend it using [`LeafConfigList::new`]
10//! or [`LeafConfigList::extend`]:
11//!
12//! ```rust,ignore
13//! use cotis_modules_debug::serialize_targets::leaf_serialization::{LeafConfigList, LeafConfigListNil};
14//!
15//! // Single leaf type:
16//! type Leafs = LeafConfigList<TextElementConfig<'static>, LeafConfigListNil>;
17//! let leafs = Leafs::new();
18//!
19//! // Multiple leaf types (order matters — see below):
20//! type Leafs = LeafConfigList<
21//!     TextElementConfig<'static>,
22//!     LeafConfigList<ImageElementConfig<'static>, LeafConfigListNil>,
23//! >;
24//! let leafs = LeafConfigList::<TextElementConfig<'static>, _>::extend();
25//! ```
26//!
27//! # Deserialization order
28//!
29//! When replaying a [`LeafConfig`](super::SerializableInterfaceInstructions::LeafConfig)
30//! instruction, the list tries to deserialize the JSON blob as the head type first. If that
31//! fails, it recurses to the tail. **Put more specific leaf types before more general ones**
32//! to avoid ambiguous matches.
33
34use std::marker::PhantomData;
35
36use cotis::element_configuring::{
37    ConfigureElements, ConfigureLeafElements, OpenElement, OpenLeafElement,
38};
39use serde::de::DeserializeOwned;
40use serde::{Deserialize, Serialize};
41
42/// Terminal node of a [`LeafConfigList`] chain (empty list).
43#[derive(Clone, Copy, Debug)]
44pub struct LeafConfigListNil;
45
46/// A type-level cons-list of leaf config types used for replay deserialization.
47///
48/// This is a phantom type: it carries no runtime data. Use [`new`](Self::new) to start a
49/// one-element list, [`extend`](Self::extend) to prepend another type, or
50/// [`new_long`](Self::new_long) when the full `Head`/`Tail` types are already known.
51#[derive(Debug)]
52pub struct LeafConfigList<Head, Tail>(PhantomData<(Head, Tail)>);
53
54impl<Head, Tail> Clone for LeafConfigList<Head, Tail> {
55    fn clone(&self) -> Self {
56        *self
57    }
58}
59
60impl<Head, Tail> Copy for LeafConfigList<Head, Tail> {}
61
62impl<Head, Tail> LeafConfigList<Head, Tail> {
63    /// Creates a one-element list with `Head` as the only leaf type.
64    pub fn new() -> LeafConfigList<Head, LeafConfigListNil> {
65        LeafConfigList(PhantomData)
66    }
67
68    /// Prepends `Head` to an existing `Head2`/`Tail2` list.
69    pub fn extend<Head2, Tail2>() -> LeafConfigList<Head, LeafConfigList<Head2, Tail2>> {
70        LeafConfigList(PhantomData)
71    }
72
73    /// Creates a list with the `Head` and `Tail` types already specified in the struct.
74    pub fn new_long() -> LeafConfigList<Head, Tail> {
75        LeafConfigList(PhantomData)
76    }
77}
78
79/// Type-erased JSON storage for a leaf element config recorded during stage 1.
80///
81/// The concrete type is recovered at replay time using a [`LeafConfigList`] chain.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct LeafConfigData {
84    internal: Vec<u8>,
85}
86
87impl LeafConfigData {
88    pub(crate) fn new<Head: Serialize>(config: &Head) -> LeafConfigData {
89        Self {
90            internal: serde_json::to_vec(&config).unwrap(),
91        }
92    }
93
94    fn try_reconstruct<Head: DeserializeOwned>(&self) -> Option<Head> {
95        serde_json::from_slice(&self.internal).ok()
96    }
97}
98
99/// Type-level dispatch for replaying [`LeafConfigData`] against open elements.
100///
101/// Implemented automatically for [`LeafConfigList`] chains. Custom implementations are
102/// rarely needed; build a [`LeafConfigList`] instead.
103pub trait LeafConfigListCompatible<Configurer, ConfigType>
104where
105    Configurer: ConfigureElements<ConfigType>,
106{
107    /// Applies a leaf config blob to an open leaf element.
108    fn configure(
109        data: &LeafConfigData,
110        configurer: &mut OpenLeafElement<'_, Configurer, ConfigType>,
111    );
112
113    /// Applies a leaf config blob to an open element (consuming it).
114    fn configure_open(data: &LeafConfigData, configurer: OpenElement<'_, Configurer, ConfigType>);
115}
116
117impl<Head, Tail, Configurer, ConfigType> LeafConfigListCompatible<Configurer, ConfigType>
118    for LeafConfigList<Head, Tail>
119where
120    Configurer: ConfigureLeafElements<Head> + ConfigureElements<ConfigType>,
121    Tail: LeafConfigListCompatible<Configurer, ConfigType>,
122    Head: DeserializeOwned,
123{
124    fn configure(
125        data: &LeafConfigData,
126        configurer: &mut OpenLeafElement<'_, Configurer, ConfigType>,
127    ) {
128        let reconstruction: Option<Head> = data.try_reconstruct();
129        match reconstruction {
130            Some(reconstruction) => {
131                configurer.set_leaf_config(reconstruction);
132            }
133            None => {
134                Tail::configure(data, configurer);
135            }
136        }
137    }
138
139    fn configure_open(data: &LeafConfigData, configurer: OpenElement<'_, Configurer, ConfigType>) {
140        let reconstruction: Option<Head> = data.try_reconstruct();
141        match reconstruction {
142            Some(reconstruction) => {
143                configurer.set_leaf_config(reconstruction);
144            }
145            None => {
146                Tail::configure_open(data, configurer);
147            }
148        }
149    }
150}
151
152impl<Configurer, ConfigType> LeafConfigListCompatible<Configurer, ConfigType> for LeafConfigListNil
153where
154    Configurer: ConfigureElements<ConfigType>,
155{
156    fn configure(
157        _data: &LeafConfigData,
158        _configurer: &mut OpenLeafElement<'_, Configurer, ConfigType>,
159    ) {
160    }
161
162    fn configure_open(_data: &LeafConfigData, configurer: OpenElement<'_, Configurer, ConfigType>) {
163        drop(configurer);
164    }
165}