#[doc(hidden)]
pub mod __internal;
mod definition;
mod feattle_value;
pub mod json_reading;
pub mod last_reload;
#[doc(hidden)]
pub mod macros;
pub mod persist;
use crate::__internal::{FeattlesStruct, InnerFeattles};
use crate::json_reading::FromJsonError;
use crate::last_reload::LastReload;
use async_trait::async_trait;
use chrono::Utc;
pub use definition::*;
pub use feattle_value::*;
use parking_lot::{MappedRwLockReadGuard, RwLockReadGuard, RwLockWriteGuard};
use persist::*;
use serde_json::Value;
use std::error::Error;
use std::fmt::Debug;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum UpdateError<PersistError: Error + Send + Sync + 'static> {
#[error("cannot update because current values were never successfully loaded from the persist layer")]
NeverReloaded,
#[error("the key {0} is unknown")]
UnknownKey(String),
#[error("failed to parse the value from JSON")]
Parsing(
#[source]
#[from]
FromJsonError,
),
#[error("failed to persist new state")]
Persistence(#[source] PersistError),
}
#[derive(Error, Debug)]
pub enum HistoryError<PersistError: Error + Send + Sync + 'static> {
#[error("the key {0} is unknown")]
UnknownKey(String),
#[error("failed to load persisted state")]
Persistence(#[source] PersistError),
}
#[async_trait]
pub trait Feattles<P>: FeattlesPrivate<P> {
fn new(persistence: P) -> Self;
fn persistence(&self) -> &P;
fn keys(&self) -> &'static [&'static str];
fn definition(&self, key: &str) -> Option<FeattleDefinition>;
fn last_reload(&self) -> LastReload {
self._read().last_reload
}
fn current_values(&self) -> Option<MappedRwLockReadGuard<CurrentValues>> {
let inner = self._read();
match inner.current_values.as_ref() {
None => None,
Some(_) => Some(RwLockReadGuard::map(inner, |x| {
x.current_values.as_ref().unwrap()
})),
}
}
async fn reload(&self) -> Result<(), P::Error>
where
P: Persist + Sync + 'static,
{
let current_values = self.persistence().load_current().await?;
let mut inner = self._write();
let now = Utc::now();
match current_values {
None => {
inner.last_reload = LastReload::NoData { reload_date: now };
let empty = CurrentValues {
version: 0,
date: now,
feattles: Default::default(),
};
inner.current_values = Some(empty);
}
Some(current_values) => {
inner.last_reload = LastReload::Data {
reload_date: now,
version: current_values.version,
version_date: current_values.date,
};
for &key in self.keys() {
let value = current_values.feattles.get(key).cloned();
log::debug!("Will update {} with {:?}", key, value);
if let Err(error) = inner.feattles_struct.try_update(key, value) {
log::error!("Failed to update {}: {:?}", key, error);
}
}
inner.current_values = Some(current_values);
}
}
Ok(())
}
async fn update(
&self,
key: &str,
value: Value,
modified_by: String,
) -> Result<(), UpdateError<P::Error>>
where
P: Persist + Sync + 'static,
{
use UpdateError::*;
if !self.keys().contains(&key) {
return Err(UnknownKey(key.to_owned()));
}
let new_value = CurrentValue {
modified_at: Utc::now(),
modified_by,
value,
};
let (new_values, old_value) = {
let mut inner = self._write();
let mut new_values = inner.current_values.clone().ok_or(NeverReloaded)?;
new_values
.feattles
.insert(key.to_owned(), new_value.clone());
new_values.version += 1;
let old_value = inner
.feattles_struct
.try_update(key, Some(new_value.clone()))?;
(new_values, old_value)
};
log::debug!("new_values = {:?}", new_values);
let rollback_step_1 = || {
let _ = self
._write()
.feattles_struct
.try_update(key, old_value.clone());
};
let persistence = self.persistence();
let old_history = persistence
.load_history(key)
.await
.map_err(|err| {
rollback_step_1();
Persistence(err)
})?
.unwrap_or_default();
let new_definition = self
.definition(key)
.expect("the key is guaranteed to exist");
let mut new_history = old_history.clone();
new_history.entries.push(HistoryEntry {
value: new_value.value.clone(),
value_overview: new_definition.value_overview,
modified_at: new_value.modified_at,
modified_by: new_value.modified_by.clone(),
});
persistence
.save_history(key, &new_history)
.await
.map_err(|err| {
rollback_step_1();
Persistence(err)
})?;
if let Err(err) = persistence.save_current(&new_values).await {
rollback_step_1();
if let Err(err) = self.persistence().save_history(key, &old_history).await {
log::warn!("Failed to rollback history for {}: {:?}", key, err);
}
return Err(Persistence(err));
}
self._write().current_values = Some(new_values);
Ok(())
}
fn definitions(&self) -> Vec<FeattleDefinition> {
self.keys()
.iter()
.map(|&key| {
self.definition(key)
.expect("since we iterate over the list of known keys, this should always work")
})
.collect()
}
async fn history(&self, key: &str) -> Result<ValueHistory, HistoryError<P::Error>>
where
P: Persist + Sync + 'static,
{
if !self.keys().contains(&key) {
return Err(HistoryError::UnknownKey(key.to_owned()));
}
let history = self
.persistence()
.load_history(key)
.await
.map_err(HistoryError::Persistence)?;
Ok(history.unwrap_or_default())
}
}
#[doc(hidden)]
pub trait FeattlesPrivate<P> {
type FeattleStruct: FeattlesStruct;
fn _read(&self) -> RwLockReadGuard<InnerFeattles<Self::FeattleStruct>>;
fn _write(&self) -> RwLockWriteGuard<InnerFeattles<Self::FeattleStruct>>;
}
#[cfg(test)]
mod tests {
use super::*;
use parking_lot::Mutex;
use serde_json::json;
use std::collections::BTreeMap;
use std::sync::Arc;
#[derive(Debug, thiserror::Error)]
#[error("Some error")]
struct SomeError;
#[derive(Default, Clone)]
struct MockPersistence(Arc<Mutex<MockPersistenceInner>>);
#[derive(Default)]
struct MockPersistenceInner {
current: Option<CurrentValues>,
history: BTreeMap<String, ValueHistory>,
next_error: Option<SomeError>,
}
impl MockPersistence {
fn put_error(&self) {
let previous = self.0.lock().next_error.replace(SomeError);
assert!(previous.is_none());
}
fn get_error(&self) -> Result<(), SomeError> {
match self.0.lock().next_error.take() {
None => Ok(()),
Some(e) => Err(e),
}
}
fn unwrap_current(&self) -> CurrentValues {
self.0.lock().current.clone().unwrap()
}
fn unwrap_history(&self, key: &str) -> ValueHistory {
self.0.lock().history.get(key).cloned().unwrap()
}
}
#[async_trait]
impl Persist for MockPersistence {
type Error = SomeError;
async fn save_current(&self, value: &CurrentValues) -> Result<(), Self::Error> {
self.get_error().map(|_| {
self.0.lock().current = Some(value.clone());
})
}
async fn load_current(&self) -> Result<Option<CurrentValues>, Self::Error> {
self.get_error().map(|_| self.0.lock().current.clone())
}
async fn save_history(&self, key: &str, value: &ValueHistory) -> Result<(), Self::Error> {
self.get_error().map(|_| {
self.0.lock().history.insert(key.to_owned(), value.clone());
})
}
async fn load_history(&self, key: &str) -> Result<Option<ValueHistory>, Self::Error> {
self.get_error()
.map(|_| self.0.lock().history.get(key).cloned())
}
}
#[tokio::test]
async fn test() {
feattles! {
struct Config {
a: i32,
b: i32 = 17
}
}
let persistence = MockPersistence::default();
let config = Config::new(persistence.clone());
assert_eq!(*config.a(), 0);
assert_eq!(*config.b(), 17);
assert!(Arc::ptr_eq(&config.persistence().0, &persistence.0));
assert_eq!(config.keys(), &["a", "b"]);
assert!(config.last_reload() == LastReload::Never);
assert!(config.current_values().is_none());
config.reload().await.unwrap();
assert_eq!(*config.a(), 0);
assert_eq!(*config.b(), 17);
let last_reload = config.last_reload();
assert!(matches!(last_reload, LastReload::NoData { .. }));
assert!(config.current_values().is_some());
persistence.put_error();
config.reload().await.unwrap_err();
assert_eq!(config.last_reload(), last_reload);
config
.update("a", json!(27i32), "somebody".to_owned())
.await
.unwrap();
assert_eq!(*config.a(), 27);
let values = persistence.unwrap_current();
assert_eq!(values.version, 1);
let value = values.feattles.get("a").unwrap();
assert_eq!(value.modified_by, "somebody");
assert_eq!(value.value, json!(27i32));
let history = persistence.unwrap_history("a");
assert_eq!(history.entries.len(), 1);
assert_eq!(&history.entries[0].value, &json!(27i32));
assert_eq!(&history.entries[0].value_overview, "27");
assert_eq!(&history.entries[0].modified_by, "somebody");
persistence.put_error();
config
.update("a", json!(207i32), "somebody else".to_owned())
.await
.unwrap_err();
assert_eq!(*config.a(), 27);
let values = persistence.unwrap_current();
assert_eq!(values.version, 1);
let value = values.feattles.get("a").unwrap();
assert_eq!(value.modified_by, "somebody");
assert_eq!(value.value, json!(27i32));
let history = persistence.unwrap_history("a");
assert_eq!(history.entries.len(), 1);
assert_eq!(&history.entries[0].value, &json!(27i32));
assert_eq!(&history.entries[0].value_overview, "27");
assert_eq!(&history.entries[0].modified_by, "somebody");
}
}