feattle_core/
persist.rs

1//! Define the interface with some external persistence logic
2//!
3//! This core module does not provide any concrete implementation for persisting the current and
4//! historical values for the feattles. Instead, it defines this extension point that can be
5//! used to create your own custom logic, however some implementors are available in the package
6//! `feattle-sync`.
7
8use crate::BoxError;
9use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::BTreeMap;
14
15/// Responsible for storing and loading data from a permanent storage.
16///
17/// # Async
18/// The methods on this trait are async and can be implemented with the help of the `async_trait`
19/// crate:
20///
21/// ```
22/// use async_trait::async_trait;
23/// use feattle_core::BoxError;
24/// use feattle_core::persist::*;
25///
26/// struct MyPersistenceLogic;
27///
28/// #[async_trait]
29/// impl Persist for MyPersistenceLogic {
30///     async fn save_current(&self, value: &CurrentValues) -> Result<(), BoxError> {
31///         unimplemented!()
32///     }
33///
34///     async fn load_current(&self) -> Result<Option<CurrentValues>, BoxError> {
35///         unimplemented!()
36///     }
37///
38///     async fn save_history(&self, key: &str, value: &ValueHistory) -> Result<(), BoxError> {
39///         unimplemented!()
40///     }
41///
42///     async fn load_history(&self, key: &str) -> Result<Option<ValueHistory>, BoxError> {
43///         unimplemented!()
44///     }
45/// }
46/// ```
47///
48/// # Errors
49/// The persistence layer can return an error, that will be bubbled up by other error
50/// types, like [`super::UpdateError`] and [`super::HistoryError`].
51#[async_trait]
52pub trait Persist: Send + Sync {
53    /// Save current state of all feattles.
54    async fn save_current(&self, value: &CurrentValues) -> Result<(), BoxError>;
55
56    /// Load the current state of all feattles. With no previous state existed, `Ok(None)` should be
57    /// returned.
58    async fn load_current(&self) -> Result<Option<CurrentValues>, BoxError>;
59
60    /// Save the full history of a single feattle.
61    async fn save_history(&self, key: &str, value: &ValueHistory) -> Result<(), BoxError>;
62
63    /// Load the full history of a single feattle. With the feattle has no history, `Ok(None)`
64    /// should be returned.
65    async fn load_history(&self, key: &str) -> Result<Option<ValueHistory>, BoxError>;
66}
67
68/// Store the current values of all feattles
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct CurrentValues {
71    /// A monotonically increasing version, that can be used to detect race conditions
72    pub version: i32,
73    /// When this version was created
74    pub date: DateTime<Utc>,
75    /// Data for each feattle. Some feattles may not be present in this map, since they were never
76    /// modified. Also, some extra feattles may be present in this map because they were used in a
77    /// previous invocation of feattles.
78    pub feattles: BTreeMap<String, CurrentValue>,
79}
80
81/// Store the current value of a single featttle
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83pub struct CurrentValue {
84    /// When this modification was made
85    pub modified_at: DateTime<Utc>,
86    /// Who did that modification
87    pub modified_by: String,
88    /// The value, expressed in JSON
89    pub value: Value,
90}
91
92/// Store the history of modification of a single feattle
93#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
94pub struct ValueHistory {
95    /// The entries are not necessarily stored in any specific order
96    pub entries: Vec<HistoryEntry>,
97}
98
99/// Store the value at a given point in time of a single feattle
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
101pub struct HistoryEntry {
102    /// The value, expressed in JSON
103    pub value: Value,
104    /// A human-readable description of the value
105    pub value_overview: String,
106    /// When this modification was made
107    pub modified_at: DateTime<Utc>,
108    /// Who did that modification
109    pub modified_by: String,
110}
111
112/// A mock implementation that does not store the information anywhere.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub struct NoPersistence;
115
116#[async_trait]
117impl Persist for NoPersistence {
118    async fn save_current(&self, _value: &CurrentValues) -> Result<(), BoxError> {
119        Ok(())
120    }
121
122    async fn load_current(&self) -> Result<Option<CurrentValues>, BoxError> {
123        Ok(None)
124    }
125
126    async fn save_history(&self, _key: &str, _value: &ValueHistory) -> Result<(), BoxError> {
127        Ok(())
128    }
129
130    async fn load_history(&self, _key: &str) -> Result<Option<ValueHistory>, BoxError> {
131        Ok(None)
132    }
133}