use std::{collections::HashMap, ops::Deref, sync::Arc};
use maf_schemas::apps::{MetaEntry, MetaVisibility};
use serde::{de::DeserializeOwned, Serialize};
use crate::{
callable::{BoxedCallable, CallableFetch},
platform::{Platform, TargetPlatform},
App,
};
#[repr(transparent)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct MetaKey(pub(crate) Arc<str>);
impl Deref for MetaKey {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Default)]
pub struct MetaStorage {
pub(crate) platform: Option<Arc<TargetPlatform>>,
pub(crate) updaters: HashMap<MetaKey, AnyMetaUpdater>,
}
#[derive(Debug, thiserror::Error)]
pub enum MetaError {
#[error("Failed to serialize JSON: {0}")]
SerializationError(serde_json::Error),
#[error("Failed to deserialize JSON: {0}")]
DeserializationError(serde_json::Error),
#[error("Meta entry not found")]
NotFound,
}
impl MetaStorage {
fn platform(&self) -> &TargetPlatform {
self.platform
.as_ref()
.expect("MetaStorage was not initialized with a platform")
}
pub fn set(
&self,
visibility: MetaVisibility,
key: &str,
value: impl Serialize,
) -> Result<Option<MetaEntry>, MetaError> {
Ok(self.platform().set_meta(
visibility,
key,
&serde_json::to_string(&value).map_err(MetaError::SerializationError)?,
))
}
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T, MetaError> {
if let Some(entry) = self.platform().get_meta(key) {
let value = entry
.deserialize()
.map_err(MetaError::DeserializationError)?;
Ok(value)
} else {
Err(MetaError::NotFound)?
}
}
pub fn get_any(&self, key: &str) -> Option<MetaEntry> {
self.platform().get_meta(key)
}
pub fn delete(&self, key: &str) -> Option<MetaEntry> {
self.platform().delete_meta(key)
}
pub fn list(&self) -> Vec<(String, MetaEntry)> {
self.platform().list_meta()
}
pub(crate) async fn update_all_meta(&self, app: App) -> anyhow::Result<()> {
for name in self.updaters.keys() {
self.trigger_meta_update(app.clone(), name).await?;
}
Ok(())
}
pub(crate) async fn trigger_meta_update(&self, app: App, name: &MetaKey) -> anyhow::Result<()> {
if let Some(updater) = self.updaters.get(name) {
let ctx = MetaContext { app };
let value = (updater.create)(ctx).await?;
self.set(updater.visibility, &name, value)?;
}
Ok(())
}
}
pub struct MetaContext {
app: App,
}
impl CallableFetch<App> for MetaContext {
fn fetch(&self) -> App {
self.app.clone()
}
}
pub struct AnyMetaUpdater {
pub(crate) _key: MetaKey,
pub(crate) visibility: MetaVisibility,
pub(crate) create: BoxedCallable<MetaContext, serde_json::Value, serde_json::Error>,
}