mod migrate_0;
use std::collections::HashMap;
use anymap::Map;
use async_trait::async_trait;
use once_cell::sync::Lazy;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::{client::storage::StorageAdapter, wallet::WalletError};
pub(crate) const MIGRATION_VERSION_KEY: &str = "migration-version";
#[cfg(feature = "stronghold")]
struct LatestBackupMigration(MigrationVersion);
static MIGRATIONS: Lazy<Map<dyn anymap::any::Any + Send + Sync>> = Lazy::new(|| {
let mut migrations = Map::new();
#[cfg(feature = "storage")]
{
use super::storage::Storage;
const STORAGE_MIGRATIONS: [(Option<usize>, &'static dyn DynMigration<Storage>); 1] = [
(None, &migrate_0::Migrate),
];
migrations.insert(std::collections::HashMap::from(STORAGE_MIGRATIONS));
}
#[cfg(feature = "stronghold")]
{
use crate::client::stronghold::StrongholdAdapter;
const BACKUP_MIGRATIONS: [(Option<usize>, &'static dyn DynMigration<StrongholdAdapter>); 1] = [
(None, &migrate_0::Migrate),
];
migrations.insert(LatestBackupMigration(BACKUP_MIGRATIONS.last().unwrap().1.version()));
migrations.insert(std::collections::HashMap::from(BACKUP_MIGRATIONS));
}
migrations
});
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct MigrationVersion {
pub id: usize,
pub sdk_version: String,
pub date: time::Date,
}
impl std::fmt::Display for MigrationVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} - {} - {}", self.id, self.sdk_version, self.date)
}
}
pub(crate) trait MigrationData {
const ID: usize;
const SDK_VERSION: &'static str;
const DATE: time::Date;
fn version() -> MigrationVersion {
MigrationVersion {
id: Self::ID,
sdk_version: Self::SDK_VERSION.to_string(),
date: Self::DATE,
}
}
}
#[async_trait]
pub(crate) trait Migration<S: StorageAdapter>: MigrationData {
async fn migrate(storage: &S) -> Result<(), WalletError>;
}
#[async_trait]
trait DynMigration<S: StorageAdapter>: Send + Sync {
fn version(&self) -> MigrationVersion;
async fn migrate(&self, storage: &S) -> Result<(), WalletError>;
}
#[async_trait]
impl<S: StorageAdapter, T: Migration<S> + Send + Sync> DynMigration<S> for T
where
WalletError: From<S::Error>,
S::Error: From<serde_json::Error>,
{
fn version(&self) -> MigrationVersion {
T::version()
}
async fn migrate(&self, storage: &S) -> Result<(), WalletError> {
let version = self.version();
log::info!("Migrating to version {}", version);
T::migrate(storage).await?;
storage.set(MIGRATION_VERSION_KEY, &version).await?;
Ok(())
}
}
pub async fn migrate<S: 'static + StorageAdapter>(storage: &S) -> Result<(), WalletError>
where
WalletError: From<S::Error>,
S::Error: From<serde_json::Error>,
{
let last_migration = storage.get::<MigrationVersion>(MIGRATION_VERSION_KEY).await?;
for migration in migrations(last_migration)? {
migration.migrate(storage).await?;
}
Ok(())
}
fn migrations<S: 'static + StorageAdapter>(
mut last_migration: Option<MigrationVersion>,
) -> Result<Vec<&'static dyn DynMigration<S>>, WalletError> {
let migrations = MIGRATIONS
.get::<HashMap<Option<usize>, &'static dyn DynMigration<S>>>()
.ok_or_else(|| {
WalletError::Migration(format!(
"invalid migration storage kind: {}",
std::any::type_name::<S>()
))
})?;
let mut res = Vec::new();
while let Some(next) = migrations.get(&last_migration.as_ref().map(|m| m.id)) {
last_migration = Some(next.version());
res.push(*next);
}
Ok(res)
}
#[cfg(feature = "stronghold")]
pub fn latest_backup_migration_version() -> MigrationVersion {
MIGRATIONS.get::<LatestBackupMigration>().unwrap().0.clone()
}
trait Convert {
type New: Serialize + DeserializeOwned;
type Old: DeserializeOwned;
fn check(value: &mut serde_json::Value) -> Result<(), WalletError> {
if Self::New::deserialize(&*value).is_err() {
*value = serde_json::to_value(Self::convert(Self::Old::deserialize(&*value)?)?)?;
}
Ok(())
}
fn convert(old: Self::Old) -> Result<Self::New, WalletError>;
}