feattle_core/
lib.rs

1//! This crate is the core implementation of the feature flags (called "feattles", for short).
2//!
3//! Its main parts are the macro [`feattles!`] together with the trait [`Feattles`]. Please refer to
4//! the [main package - `feattle`](https://crates.io/crates/feattle) for more information.
5//!
6//! # Usage example
7//! ```
8//! use std::sync::Arc;
9//! use feattle_core::{feattles, Feattles};
10//! use feattle_core::persist::NoPersistence;
11//!
12//! // Declare the struct
13//! feattles! {
14//!     struct MyFeattles {
15//!         /// Is this usage considered cool?
16//!         is_cool: bool = true,
17//!         /// Limit the number of "blings" available.
18//!         /// This will not change the number of "blengs", though!
19//!         max_blings: i32,
20//!         /// List the actions that should not be available
21//!         blocked_actions: Vec<String>,
22//!     }
23//! }
24//!
25//! // Create a new instance (`NoPersistence` is just a mock for the persistence layer)
26//! let my_feattles = MyFeattles::new(Arc::new(NoPersistence));
27//!
28//! // Read values (note the use of `*`)
29//! assert_eq!(*my_feattles.is_cool(), true);
30//! assert_eq!(*my_feattles.max_blings(), 0);
31//! assert_eq!(*my_feattles.blocked_actions(), Vec::<String>::new());
32//! ```
33//!
34//! # How it works
35//!
36//! The macro will generate a struct with the given name and visibility modifier (assuming private
37//! by default). The generated struct implements [`Feattles`] and also exposes one method for each
38//! feattle.
39//!
40//! The methods created for each feattle allow reading their current value. For example, for a
41//! feattle `is_cool: bool`, there will be a method like
42//! `pub fn is_cool(&self) -> MappedRwLockReadGuard<bool>`. Note the use of
43//! [`parking_lot::MappedRwLockReadGuard`] because the interior of the struct is stored behind a `RwLock` to
44//! control concurrent access.
45//!
46//! A feattle is created with the syntax `$key: $type [= $default]`. You can use doc coments (
47//! starting with `///`) to describe nicely what they do in your system. You can use any type that
48//! implements [`FeattleValue`] and optionally provide a default. If not provided, the default
49//! will be created with `Default::default()`.
50//!
51//! # Updating values
52//! This crate only disposes of low-level methods to load current feattles with [`Feattles::reload()`]
53//! and update their values with [`Feattles::update()`]. Please look for the crates
54//! [feattle-sync](https://crates.io/crates/feattle-sync) and
55//! [feattle-ui](https://crates.io/crates/feattle-ui) for higher-level functionalities.
56//!
57//! # Limitations
58//! Due to some restrictions on how the macro is written, you can only use [`feattles!`] once per
59//! module. For example, the following does not compile:
60//!
61//! ```compile_fail
62//! use feattle_core::feattles;
63//!
64//! feattles! { struct A { } }
65//! feattles! { struct B { } }
66//! ```
67//!
68//! You can work around this limitation by creating a sub-module and then re-exporting the generated
69//! struct. Note the use of `pub struct` in the second case.
70//! ```
71//! use feattle_core::feattles;
72//!
73//! feattles! { struct A { } }
74//!
75//! mod b {
76//!     use feattle_core::feattles;
77//!     feattles! { pub struct B { } }
78//! }
79//!
80//! use b::B;
81//! ```
82//!
83//! # Optional features
84//!
85//! - **uuid**: will add support for [`uuid::Uuid`].
86
87#[doc(hidden)]
88pub mod __internal;
89mod definition;
90mod feattle_value;
91pub mod json_reading;
92pub mod last_reload;
93/// This module only contains exported macros, that are documented at the root level.
94#[doc(hidden)]
95pub mod macros;
96pub mod persist;
97
98use crate::__internal::{FeattlesStruct, InnerFeattles};
99use crate::json_reading::FromJsonError;
100use crate::last_reload::LastReload;
101use async_trait::async_trait;
102use chrono::Utc;
103pub use definition::*;
104pub use feattle_value::*;
105use parking_lot::{MappedRwLockReadGuard, RwLockReadGuard, RwLockWriteGuard};
106use persist::*;
107use serde_json::Value;
108use std::error::Error;
109use std::fmt::Debug;
110use std::sync::Arc;
111use thiserror::Error;
112
113/// Represents a type-erased error that comes from some external source
114pub type BoxError = Box<dyn Error + Send + Sync>;
115
116/// The error type returned by [`Feattles::update()`]
117#[derive(Error, Debug)]
118pub enum UpdateError {
119    /// Cannot update because current values were never successfully loaded from the persist layer
120    #[error("cannot update because current values were never successfully loaded from the persist layer")]
121    NeverReloaded,
122    /// The key is unknown
123    #[error("the key {0} is unknown")]
124    UnknownKey(String),
125    /// Failed to parse the value from JSON
126    #[error("failed to parse the value from JSON")]
127    Parsing(
128        #[source]
129        #[from]
130        FromJsonError,
131    ),
132    /// Failed to persist new state
133    #[error("failed to persist new state")]
134    Persistence(#[source] BoxError),
135}
136
137/// The error type returned by [`Feattles::history()`]
138#[derive(Error, Debug)]
139pub enum HistoryError {
140    /// The key is unknown
141    #[error("the key {0} is unknown")]
142    UnknownKey(String),
143    /// Failed to load persisted state
144    #[error("failed to load persisted state")]
145    Persistence(#[source] BoxError),
146}
147
148/// The main trait of this crate.
149///
150/// The struct created with [`feattles!`] will implement this trait in addition to a method for each
151/// feattle. Read more at the [crate documentation](crate).
152#[async_trait]
153pub trait Feattles: FeattlesPrivate {
154    /// Create a new feattles instance, using the given persistence layer logic.
155    ///
156    /// All feattles will start with their default values. You can force an initial synchronization
157    /// with [`Feattles::update`].
158    fn new(persistence: Arc<dyn Persist>) -> Self;
159
160    /// Return a shared reference to the persistence layer.
161    fn persistence(&self) -> &Arc<dyn Persist>;
162
163    /// The list of all available keys.
164    fn keys(&self) -> &'static [&'static str];
165
166    /// Describe one specific feattle, returning `None` if the feattle with the given name does not
167    /// exist.
168    fn definition(&self, key: &str) -> Option<FeattleDefinition>;
169
170    /// Return details of the last time the data was synchronized by calling [`Feattles::reload()`].
171    fn last_reload(&self) -> LastReload {
172        self._read().last_reload
173    }
174
175    /// Return a reference to the last synchronized data. The reference is behind a
176    /// read-write lock and will block any update until it is dropped. `None` is returned if a
177    /// successful synchronization have never happened.
178    fn current_values(&self) -> Option<MappedRwLockReadGuard<CurrentValues>> {
179        let inner = self._read();
180        if inner.current_values.is_none() {
181            None
182        } else {
183            Some(RwLockReadGuard::map(inner, |x| {
184                x.current_values.as_ref().unwrap()
185            }))
186        }
187    }
188
189    /// Reload the current feattles' data from the persistence layer, propagating any errors
190    /// produced by it.
191    ///
192    /// If any of the feattle values fail to be parsed from previously persisted values, their
193    /// updates will be skipped. Other feattles that parsed successfully will still be updated.
194    /// In this case, a [`log::error!`] will be generated for each time it occurs.
195    async fn reload(&self) -> Result<(), BoxError> {
196        let current_values = self.persistence().load_current().await?;
197        let mut inner = self._write();
198        let now = Utc::now();
199        match current_values {
200            None => {
201                inner.last_reload = LastReload::NoData { reload_date: now };
202                let empty = CurrentValues {
203                    version: 0,
204                    date: now,
205                    feattles: Default::default(),
206                };
207                inner.current_values = Some(empty);
208            }
209            Some(current_values) => {
210                inner.last_reload = LastReload::Data {
211                    reload_date: now,
212                    version: current_values.version,
213                    version_date: current_values.date,
214                };
215                for &key in self.keys() {
216                    let value = current_values.feattles.get(key).cloned();
217                    log::debug!("Will update {} with {:?}", key, value);
218                    if let Err(error) = inner.feattles_struct.try_update(key, value) {
219                        log::error!("Failed to update {}: {:?}", key, error);
220                    }
221                }
222                inner.current_values = Some(current_values);
223            }
224        }
225        Ok(())
226    }
227
228    /// Update a single feattle, passing the new value (in JSON representation) and the user that
229    /// is associated with this change. The change will be persisted directly.
230    ///
231    /// While the update is happening, the new value will already be observable from other
232    /// execution tasks or threads. However, if the update fails, the change will be rolled back.
233    ///
234    /// # Consistency
235    ///
236    /// To avoid operating on stale data, before doing an update the caller should usually call
237    /// [`Feattles::reload()`] to ensure data is current.
238    async fn update(
239        &self,
240        key: &str,
241        value: Value,
242        modified_by: String,
243    ) -> Result<(), UpdateError> {
244        use UpdateError::*;
245
246        // The update operation is made of 4 steps, each of which may fail:
247        // 1. parse and update the inner generic struct
248        // 2. persist the new history entry
249        // 3. persist the new current values
250        // 4. update the copy of the current values
251        // If any step fails, the others will be rolled back
252
253        // Assert the key exists
254        if !self.keys().contains(&key) {
255            return Err(UnknownKey(key.to_owned()));
256        }
257
258        let new_value = CurrentValue {
259            modified_at: Utc::now(),
260            modified_by,
261            value,
262        };
263
264        let (new_values, old_value) = {
265            let mut inner = self._write();
266
267            // Check error condition for step 4 and prepare the new instance
268            let mut new_values = inner.current_values.clone().ok_or(NeverReloaded)?;
269            new_values
270                .feattles
271                .insert(key.to_owned(), new_value.clone());
272            new_values.version += 1;
273
274            // Step 1
275            let old_value = inner
276                .feattles_struct
277                .try_update(key, Some(new_value.clone()))?;
278
279            (new_values, old_value)
280        };
281
282        log::debug!("new_values = {:?}", new_values);
283
284        let rollback_step_1 = || {
285            // Note that if the old value was failing to parse, then the update will be final.
286            let _ = self
287                ._write()
288                .feattles_struct
289                .try_update(key, old_value.clone());
290        };
291
292        // Step 2: load + modify + save history
293        let persistence = self.persistence();
294        let old_history = persistence
295            .load_history(key)
296            .await
297            .map_err(|err| {
298                rollback_step_1();
299                Persistence(err)
300            })?
301            .unwrap_or_default();
302
303        // Prepare updated history
304        let new_definition = self
305            .definition(key)
306            .expect("the key is guaranteed to exist");
307        let mut new_history = old_history.clone();
308        new_history.entries.push(HistoryEntry {
309            value: new_value.value.clone(),
310            value_overview: new_definition.value_overview,
311            modified_at: new_value.modified_at,
312            modified_by: new_value.modified_by.clone(),
313        });
314
315        persistence
316            .save_history(key, &new_history)
317            .await
318            .map_err(|err| {
319                rollback_step_1();
320                Persistence(err)
321            })?;
322
323        // Step 3
324        if let Err(err) = persistence.save_current(&new_values).await {
325            rollback_step_1();
326            if let Err(err) = self.persistence().save_history(key, &old_history).await {
327                log::warn!("Failed to rollback history for {}: {:?}", key, err);
328            }
329            return Err(Persistence(err));
330        }
331
332        // Step 4
333        self._write().current_values = Some(new_values);
334
335        Ok(())
336    }
337
338    /// Return the definition for all the feattles.
339    fn definitions(&self) -> Vec<FeattleDefinition> {
340        self.keys()
341            .iter()
342            .map(|&key| {
343                self.definition(key)
344                    .expect("since we iterate over the list of known keys, this should always work")
345            })
346            .collect()
347    }
348
349    /// Return the history for a single feattle. It can be potentially empty (not entries).
350    async fn history(&self, key: &str) -> Result<ValueHistory, HistoryError> {
351        // Assert the key exists
352        if !self.keys().contains(&key) {
353            return Err(HistoryError::UnknownKey(key.to_owned()));
354        }
355
356        let history = self
357            .persistence()
358            .load_history(key)
359            .await
360            .map_err(HistoryError::Persistence)?;
361
362        Ok(history.unwrap_or_default())
363    }
364}
365
366/// This struct is `pub` because the macro must have access to it, but should be otherwise invisible
367/// to the users of this crate.
368#[doc(hidden)]
369pub trait FeattlesPrivate {
370    type FeattleStruct: FeattlesStruct;
371    fn _read(&self) -> RwLockReadGuard<InnerFeattles<Self::FeattleStruct>>;
372    fn _write(&self) -> RwLockWriteGuard<InnerFeattles<Self::FeattleStruct>>;
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use parking_lot::Mutex;
379    use serde_json::json;
380    use std::collections::BTreeMap;
381    use std::sync::Arc;
382
383    #[derive(Debug, thiserror::Error)]
384    #[error("Some error")]
385    struct SomeError;
386
387    #[derive(Default)]
388    struct MockPersistence(Mutex<MockPersistenceInner>);
389
390    #[derive(Default)]
391    struct MockPersistenceInner {
392        current: Option<CurrentValues>,
393        history: BTreeMap<String, ValueHistory>,
394        next_error: Option<BoxError>,
395    }
396
397    impl MockPersistence {
398        fn put_error(&self) {
399            let previous = self.0.lock().next_error.replace(Box::new(SomeError));
400            assert!(previous.is_none());
401        }
402
403        fn get_error(&self) -> Result<(), BoxError> {
404            match self.0.lock().next_error.take() {
405                None => Ok(()),
406                Some(e) => Err(e),
407            }
408        }
409
410        fn unwrap_current(&self) -> CurrentValues {
411            self.0.lock().current.clone().unwrap()
412        }
413
414        fn unwrap_history(&self, key: &str) -> ValueHistory {
415            self.0.lock().history.get(key).cloned().unwrap()
416        }
417    }
418
419    #[async_trait]
420    impl Persist for MockPersistence {
421        async fn save_current(&self, value: &CurrentValues) -> Result<(), BoxError> {
422            self.get_error().map(|_| {
423                self.0.lock().current = Some(value.clone());
424            })
425        }
426
427        async fn load_current(&self) -> Result<Option<CurrentValues>, BoxError> {
428            self.get_error().map(|_| self.0.lock().current.clone())
429        }
430
431        async fn save_history(&self, key: &str, value: &ValueHistory) -> Result<(), BoxError> {
432            self.get_error().map(|_| {
433                self.0.lock().history.insert(key.to_owned(), value.clone());
434            })
435        }
436
437        async fn load_history(&self, key: &str) -> Result<Option<ValueHistory>, BoxError> {
438            self.get_error()
439                .map(|_| self.0.lock().history.get(key).cloned())
440        }
441    }
442
443    #[tokio::test]
444    async fn test() {
445        feattles! {
446            struct Config {
447                /// A
448                a: i32,
449                b: i32 = 17
450            }
451        }
452
453        let persistence = Arc::new(MockPersistence::default());
454        let config = Config::new(persistence.clone());
455
456        // Initial state
457        assert_eq!(*config.a(), 0);
458        assert_eq!(*config.b(), 17);
459        assert_eq!(config.keys(), &["a", "b"]);
460        assert!(config.last_reload() == LastReload::Never);
461        assert!(config.current_values().is_none());
462
463        // Load from empty storage
464        config.reload().await.unwrap();
465        assert_eq!(*config.a(), 0);
466        assert_eq!(*config.b(), 17);
467        let last_reload = config.last_reload();
468        assert!(matches!(last_reload, LastReload::NoData { .. }));
469        assert!(config.current_values().is_some());
470
471        // Load from failing storage
472        persistence.put_error();
473        config.reload().await.unwrap_err();
474        assert_eq!(config.last_reload(), last_reload);
475
476        // Update value
477        config
478            .update("a", json!(27i32), "somebody".to_owned())
479            .await
480            .unwrap();
481        assert_eq!(*config.a(), 27);
482        let values = persistence.unwrap_current();
483        assert_eq!(values.version, 1);
484        let value = values.feattles.get("a").unwrap();
485        assert_eq!(value.modified_by, "somebody");
486        assert_eq!(value.value, json!(27i32));
487        let history = persistence.unwrap_history("a");
488        assert_eq!(history.entries.len(), 1);
489        assert_eq!(&history.entries[0].value, &json!(27i32));
490        assert_eq!(&history.entries[0].value_overview, "27");
491        assert_eq!(&history.entries[0].modified_by, "somebody");
492
493        // Failed to update
494        persistence.put_error();
495        config
496            .update("a", json!(207i32), "somebody else".to_owned())
497            .await
498            .unwrap_err();
499        assert_eq!(*config.a(), 27);
500        let values = persistence.unwrap_current();
501        assert_eq!(values.version, 1);
502        let value = values.feattles.get("a").unwrap();
503        assert_eq!(value.modified_by, "somebody");
504        assert_eq!(value.value, json!(27i32));
505        let history = persistence.unwrap_history("a");
506        assert_eq!(history.entries.len(), 1);
507        assert_eq!(&history.entries[0].value, &json!(27i32));
508        assert_eq!(&history.entries[0].value_overview, "27");
509        assert_eq!(&history.entries[0].modified_by, "somebody");
510    }
511}