Skip to main content

selene_persist/
provider.rs

1//! Recovery provider trait and registry.
2
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use selene_core::Change;
7
8use crate::{PersistError, PersistResult};
9
10/// Boxed dynamic error returned by recovery providers.
11pub type RecoveryError = Box<dyn std::error::Error + Send + Sync + 'static>;
12
13/// Result alias used by the [`RecoveryProvider`] surface.
14pub type RecoveryResult<T> = Result<T, RecoveryError>;
15
16/// Recovery-time consumer of snapshot section bytes and WAL change events.
17///
18/// Implementations own any in-memory state they materialize from section bytes
19/// and changes. The trait uses only raw tags and core change payloads so
20/// `selene-persist` stays graph-blind.
21pub trait RecoveryProvider: Send + Sync {
22    /// Stable four-byte tag identifying this provider in snapshot section tables.
23    fn provider_tag(&self) -> [u8; 4];
24
25    /// Deliver the already-decompressed bytes of one snapshot section.
26    ///
27    /// # Errors
28    ///
29    /// Returns provider-owned errors when the section cannot be applied.
30    fn read_section(&self, sub: [u8; 4], bytes: &[u8]) -> RecoveryResult<()>;
31
32    /// Deliver one committed mutation from WAL replay.
33    ///
34    /// This is a per-change convenience over the batch entry point. WAL replay
35    /// itself only ever calls [`Self::on_changes`]; this method exists for
36    /// providers (and tests) that prefer to apply changes one at a time. The
37    /// default delegates to [`Self::on_changes`] with a single-element batch, so
38    /// a provider that overrides only `on_changes` need not implement it.
39    ///
40    /// # Errors
41    ///
42    /// Returns provider-owned errors when the change cannot be applied.
43    fn on_change(&self, change: &Change) -> RecoveryResult<()> {
44        self.on_changes(std::slice::from_ref(change))
45    }
46
47    /// Deliver one committed WAL entry as a batch. This is the sole entry point
48    /// WAL replay invokes, and the only required method of the trait.
49    ///
50    /// The default implementation preserves the historical per-change contract
51    /// by calling [`Self::on_change`] for every change in entry order. A provider
52    /// must override exactly one of `on_change` / `on_changes`: override
53    /// `on_changes` for commit-level ordering, or `on_change` for the per-change
54    /// shape (the common case). Overriding neither is a logic error — the two
55    /// defaults would recurse.
56    ///
57    /// # Errors
58    ///
59    /// Returns provider-owned errors when any change in the batch cannot be
60    /// applied.
61    fn on_changes(&self, changes: &[Change]) -> RecoveryResult<()> {
62        for change in changes {
63            self.on_change(change)?;
64        }
65        Ok(())
66    }
67}
68
69/// Recovery-time provider lookup table.
70///
71/// Providers are keyed by their four-byte `provider_tag`. The internal map is
72/// ordered so WAL replay fans out changes deterministically.
73#[derive(Default)]
74pub struct ProviderRegistry {
75    providers: BTreeMap<[u8; 4], Arc<dyn RecoveryProvider>>,
76}
77
78impl ProviderRegistry {
79    /// Construct an empty provider registry.
80    #[must_use]
81    pub const fn new() -> Self {
82        Self {
83            providers: BTreeMap::new(),
84        }
85    }
86
87    /// Register a provider by its stable tag.
88    ///
89    /// # Errors
90    ///
91    /// Returns [`PersistError::DuplicateProviderTag`] if another provider with
92    /// the same tag is already registered.
93    pub fn register(&mut self, provider: Arc<dyn RecoveryProvider>) -> PersistResult<()> {
94        let tag = provider.provider_tag();
95        if self.providers.contains_key(&tag) {
96            return Err(PersistError::DuplicateProviderTag { tag });
97        }
98        self.providers.insert(tag, provider);
99        Ok(())
100    }
101
102    /// Look up a provider by tag.
103    #[must_use]
104    pub fn lookup(&self, provider_tag: [u8; 4]) -> Option<&Arc<dyn RecoveryProvider>> {
105        self.providers.get(&provider_tag)
106    }
107
108    /// Return the number of registered providers.
109    #[must_use]
110    pub fn len(&self) -> usize {
111        self.providers.len()
112    }
113
114    /// Return true when no providers are registered.
115    #[must_use]
116    pub fn is_empty(&self) -> bool {
117        self.providers.is_empty()
118    }
119
120    /// Iterate registered providers in provider-tag ascending order.
121    pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn RecoveryProvider>> + '_ {
122        self.providers.values()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    struct DummyProvider {
131        tag: [u8; 4],
132    }
133
134    impl RecoveryProvider for DummyProvider {
135        fn provider_tag(&self) -> [u8; 4] {
136            self.tag
137        }
138
139        fn read_section(&self, _sub: [u8; 4], _bytes: &[u8]) -> RecoveryResult<()> {
140            Ok(())
141        }
142
143        fn on_change(&self, _change: &Change) -> RecoveryResult<()> {
144            Ok(())
145        }
146    }
147
148    fn provider(tag: [u8; 4]) -> Arc<dyn RecoveryProvider> {
149        Arc::new(DummyProvider { tag })
150    }
151
152    #[test]
153    fn register_unique_tag_succeeds() {
154        let mut registry = ProviderRegistry::new();
155        registry.register(provider(*b"CORE")).unwrap();
156        assert_eq!(registry.len(), 1);
157    }
158
159    #[test]
160    fn register_duplicate_tag_is_rejected() {
161        let mut registry = ProviderRegistry::new();
162        registry.register(provider(*b"CORE")).unwrap();
163        assert!(matches!(
164            registry.register(provider(*b"CORE")),
165            Err(PersistError::DuplicateProviderTag { tag }) if tag == *b"CORE"
166        ));
167    }
168
169    #[test]
170    fn lookup_returns_registered_provider() {
171        let mut registry = ProviderRegistry::new();
172        registry.register(provider(*b"CORE")).unwrap();
173        assert_eq!(registry.lookup(*b"CORE").unwrap().provider_tag(), *b"CORE");
174        assert!(registry.lookup(*b"MISS").is_none());
175    }
176
177    #[test]
178    fn iter_yields_in_provider_tag_order() {
179        let mut registry = ProviderRegistry::new();
180        registry.register(provider(*b"DEMO")).unwrap();
181        registry.register(provider(*b"CORE")).unwrap();
182        registry.register(provider(*b"META")).unwrap();
183        let tags: Vec<_> = registry
184            .iter()
185            .map(|provider| provider.provider_tag())
186            .collect();
187        assert_eq!(tags, vec![*b"CORE", *b"DEMO", *b"META"]);
188    }
189}