1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Define the interface with some external persistence logic
//!
//! This core module does not provide any concrete implementation for persisting the current and
//! historical values for the feature toggles. Instead, it defines this extension point that can be
//! used to create your own custom logic, however some implementors are available in the package
//! `feattle-sync`.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::error::Error;

/// Responsible for storing and loading data from a permanent storage.
///
/// # Async
/// The methods on this trait are async and can be implemented with the help of the `async_trait`
/// crate:
///
/// ```
/// use async_trait::async_trait;
/// use feattle_core::persist::*;
///
/// struct MyPersistenceLogic;
///
/// #[async_trait]
/// impl Persist for MyPersistenceLogic {
///     type Error = std::io::Error;
///
///     async fn save_current(&self, value: &CurrentValues) -> Result<(), Self::Error> {
///         unimplemented!()
///     }
///
///     async fn load_current(&self) -> Result<Option<CurrentValues>, Self::Error> {
///         unimplemented!()
///     }
///
///     async fn save_history(&self, key: &str, value: &ValueHistory) -> Result<(), Self::Error> {
///         unimplemented!()
///     }
///
///     async fn load_history(&self, key: &str) -> Result<Option<ValueHistory>, Self::Error> {
///         unimplemented!()
///     }
/// }
/// ```
///
/// # Errors
/// The persistence layer can define their own error type, that will be bubbled up by other error
/// types, like [`super::UpdateError`] and [`super::HistoryError`].
#[async_trait]
pub trait Persist: Send + Sync + 'static {
    type Error: Error + Send + Sync + 'static;

    /// Save current state of all feattles.
    async fn save_current(&self, value: &CurrentValues) -> Result<(), Self::Error>;

    /// Load the current state of all feattles. With no previous state existed, `Ok(None)` should be
    /// returned.
    async fn load_current(&self) -> Result<Option<CurrentValues>, Self::Error>;

    /// Save the full history of a single feattle.
    async fn save_history(&self, key: &str, value: &ValueHistory) -> Result<(), Self::Error>;

    /// Load the full history of a single feattle. With the feattle has no history, `Ok(None)`
    /// should be returned.
    async fn load_history(&self, key: &str) -> Result<Option<ValueHistory>, Self::Error>;
}

/// Store the current values of all feature toggles
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrentValues {
    /// A monotonically increasing version, that can be used to detect race conditions
    pub version: i32,
    /// When this version was created
    pub date: DateTime<Utc>,
    /// Data for each feature. Some features may not be present in this map, since they were never
    /// modified. Also, some extra features may be present in this map because they were used in a
    /// previous invocation of feattles.
    pub feattles: BTreeMap<String, CurrentValue>,
}

/// Store the current value of a single feature toggle
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrentValue {
    /// When this modification was made
    pub modified_at: DateTime<Utc>,
    /// Who did that modification
    pub modified_by: String,
    /// The value, expressed in JSON
    pub value: Value,
}

/// Store the history of modification of a single feature toggle
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ValueHistory {
    /// The entries are not necessarily stored in any specific order
    pub entries: Vec<HistoryEntry>,
}

/// Store the value at a given point in time of a single feature toggle
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryEntry {
    /// The value, expressed in JSON
    pub value: Value,
    /// A human-readable description of the value
    pub value_overview: String,
    /// When this modification was made
    pub modified_at: DateTime<Utc>,
    /// Who did that modification
    pub modified_by: String,
}

/// A mock implementation that does not store the information anywhere.
pub struct NoPersistence;

#[async_trait]
impl Persist for NoPersistence {
    type Error = std::io::Error;

    async fn save_current(&self, _value: &CurrentValues) -> Result<(), Self::Error> {
        Ok(())
    }

    async fn load_current(&self) -> Result<Option<CurrentValues>, Self::Error> {
        Ok(None)
    }

    async fn save_history(&self, _key: &str, _value: &ValueHistory) -> Result<(), Self::Error> {
        Ok(())
    }

    async fn load_history(&self, _key: &str) -> Result<Option<ValueHistory>, Self::Error> {
        Ok(None)
    }
}