embedded_shadow/
builder.rs

1use core::marker::PhantomData;
2
3use bitmaps::{Bits, BitsImpl};
4
5use crate::{
6    persist::{NoPersist, PersistTrigger},
7    policy::{AccessPolicy, AllowAllPolicy, NoPersistPolicy, PersistPolicy},
8    storage::ShadowStorage,
9};
10
11// Builder states
12pub struct NeedTotalSize;
13pub struct NeedBlockSize;
14pub struct NeedBlockCount;
15pub struct NeedAccessPolicy;
16pub struct NeedPersistPolicy;
17pub struct NeedPersistTrigger;
18pub struct Ready;
19
20#[derive(Default)]
21pub struct ShadowStorageBuilder<
22    const TS: usize,
23    const BS: usize,
24    const BC: usize,
25    AP,
26    PP,
27    PT,
28    PK,
29    State,
30> {
31    access_policy: Option<AP>,
32    persist_policy: Option<PP>,
33    persist_trigger: Option<PT>,
34    _phantom: PhantomData<(PK, State)>,
35}
36
37// Start the builder
38impl ShadowStorageBuilder<0, 0, 0, (), (), (), (), NeedTotalSize> {
39    pub fn new() -> Self {
40        ShadowStorageBuilder {
41            access_policy: None,
42            persist_policy: None,
43            persist_trigger: None,
44            _phantom: PhantomData,
45        }
46    }
47}
48
49// Set total size
50impl ShadowStorageBuilder<0, 0, 0, (), (), (), (), NeedTotalSize> {
51    pub fn total_size<const TS: usize>(
52        self,
53    ) -> ShadowStorageBuilder<TS, 0, 0, (), (), (), (), NeedBlockSize> {
54        ShadowStorageBuilder {
55            access_policy: None,
56            persist_policy: None,
57            persist_trigger: None,
58            _phantom: PhantomData,
59        }
60    }
61}
62
63// Set block size
64impl<const TS: usize> ShadowStorageBuilder<TS, 0, 0, (), (), (), (), NeedBlockSize> {
65    pub fn block_size<const BS: usize>(
66        self,
67    ) -> ShadowStorageBuilder<TS, BS, 0, (), (), (), (), NeedBlockCount> {
68        ShadowStorageBuilder {
69            access_policy: None,
70            persist_policy: None,
71            persist_trigger: None,
72            _phantom: PhantomData,
73        }
74    }
75}
76
77// Set block count
78impl<const TS: usize, const BS: usize>
79    ShadowStorageBuilder<TS, BS, 0, (), (), (), (), NeedBlockCount>
80{
81    /// Set the number of blocks.
82    ///
83    /// # Panics
84    /// Panics at runtime if TS != BS * BC.
85    /// For a 1024-byte storage with 64-byte blocks, use BC = 16.
86    pub fn block_count<const BC: usize>(
87        self,
88    ) -> ShadowStorageBuilder<TS, BS, BC, (), (), (), (), NeedAccessPolicy> {
89        // Early validation - fail fast with clear error message
90        assert_eq!(
91            TS,
92            BS * BC,
93            "Total size {} does not match block_size {} * block_count {} = {}",
94            TS,
95            BS,
96            BC,
97            BS * BC
98        );
99
100        ShadowStorageBuilder {
101            access_policy: None,
102            persist_policy: None,
103            persist_trigger: None,
104            _phantom: PhantomData,
105        }
106    }
107}
108
109// Set access policy
110impl<const TS: usize, const BS: usize, const BC: usize>
111    ShadowStorageBuilder<TS, BS, BC, (), (), (), (), NeedAccessPolicy>
112{
113    pub fn access_policy<AP: AccessPolicy>(
114        self,
115        policy: AP,
116    ) -> ShadowStorageBuilder<TS, BS, BC, AP, (), (), (), NeedPersistPolicy> {
117        ShadowStorageBuilder {
118            access_policy: Some(policy),
119            persist_policy: None,
120            persist_trigger: None,
121            _phantom: PhantomData,
122        }
123    }
124
125    /// Use the default allow-all access policy
126    pub fn default_access(
127        self,
128    ) -> ShadowStorageBuilder<TS, BS, BC, AllowAllPolicy, (), (), (), NeedPersistPolicy> {
129        self.access_policy(AllowAllPolicy::default())
130    }
131}
132
133// Set persist policy
134impl<const TS: usize, const BS: usize, const BC: usize, AP>
135    ShadowStorageBuilder<TS, BS, BC, AP, (), (), (), NeedPersistPolicy>
136where
137    AP: AccessPolicy,
138{
139    /// Set a custom persist policy with a specific key type
140    pub fn persist_policy<PP, PK>(
141        self,
142        policy: PP,
143    ) -> ShadowStorageBuilder<TS, BS, BC, AP, PP, (), PK, NeedPersistTrigger>
144    where
145        PP: PersistPolicy<PK>,
146    {
147        ShadowStorageBuilder {
148            access_policy: self.access_policy,
149            persist_policy: Some(policy),
150            persist_trigger: None,
151            _phantom: PhantomData,
152        }
153    }
154
155    /// Use no persistence (no persist policy or trigger)
156    pub fn no_persist(
157        self,
158    ) -> ShadowStorageBuilder<TS, BS, BC, AP, NoPersistPolicy, NoPersist, (), Ready> {
159        ShadowStorageBuilder {
160            access_policy: self.access_policy,
161            persist_policy: Some(NoPersistPolicy::default()),
162            persist_trigger: Some(NoPersist),
163            _phantom: PhantomData,
164        }
165    }
166}
167
168// Set persist trigger
169impl<const TS: usize, const BS: usize, const BC: usize, AP, PP, PK>
170    ShadowStorageBuilder<TS, BS, BC, AP, PP, (), PK, NeedPersistTrigger>
171where
172    AP: AccessPolicy,
173    PP: PersistPolicy<PK>,
174{
175    /// Set the persist trigger that handles the persistence keys
176    pub fn persist_trigger<PT>(
177        self,
178        trigger: PT,
179    ) -> ShadowStorageBuilder<TS, BS, BC, AP, PP, PT, PK, Ready>
180    where
181        PT: PersistTrigger<PK>,
182    {
183        ShadowStorageBuilder {
184            access_policy: self.access_policy,
185            persist_policy: self.persist_policy,
186            persist_trigger: Some(trigger),
187            _phantom: PhantomData,
188        }
189    }
190}
191
192// Build the final storage
193impl<const TS: usize, const BS: usize, const BC: usize, AP, PP, PT, PK>
194    ShadowStorageBuilder<TS, BS, BC, AP, PP, PT, PK, Ready>
195where
196    AP: AccessPolicy,
197    PP: PersistPolicy<PK>,
198    PT: PersistTrigger<PK>,
199    BitsImpl<BC>: Bits,
200{
201    /// Build the final ShadowStorage instance
202    ///
203    /// # Panics
204    /// Panics if TS != BS * BC (validated in ShadowTable::new)
205    pub fn build(self) -> ShadowStorage<TS, BS, BC, AP, PP, PT, PK> {
206        ShadowStorage::new(
207            self.access_policy.unwrap(),
208            self.persist_policy.unwrap(),
209            self.persist_trigger.unwrap(),
210        )
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_simple_builder() {
220        let _storage = ShadowStorageBuilder::new()
221            .total_size::<1024>()
222            .block_size::<64>()
223            .block_count::<16>() // 64 * 16 = 1024
224            .default_access()
225            .no_persist()
226            .build();
227    }
228
229    #[test]
230    fn test_builder_with_custom_policies() {
231        struct TestAccessPolicy;
232        impl AccessPolicy for TestAccessPolicy {
233            fn can_read(&self, _addr: u16, _len: usize) -> bool {
234                true
235            }
236            fn can_write(&self, _addr: u16, _len: usize) -> bool {
237                true
238            }
239        }
240
241        struct TestPersistPolicy;
242        impl PersistPolicy<u32> for TestPersistPolicy {
243            fn push_persist_keys_for_range<F>(&self, _addr: u16, _len: usize, _push_key: F) -> bool
244            where
245                F: FnMut(u32),
246            {
247                false
248            }
249        }
250
251        struct TestPersistTrigger;
252        impl PersistTrigger<u32> for TestPersistTrigger {
253            fn push_key(&mut self, _key: u32) {}
254            fn request_persist(&mut self) {}
255        }
256
257        let _storage = ShadowStorageBuilder::new()
258            .total_size::<2048>()
259            .block_size::<128>()
260            .block_count::<16>() // 128 * 16 = 2048
261            .access_policy(TestAccessPolicy)
262            .persist_policy(TestPersistPolicy)
263            .persist_trigger(TestPersistTrigger)
264            .build();
265    }
266
267    #[test]
268    #[should_panic(expected = "Total size 1024 does not match block_size 64 * block_count 15")]
269    fn test_builder_panics_on_mismatch() {
270        let _storage = ShadowStorageBuilder::new()
271            .total_size::<1024>()
272            .block_size::<64>()
273            .block_count::<15>() // 64 * 15 = 960, not 1024!
274            .default_access()
275            .no_persist()
276            .build();
277    }
278}