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}