use super::MigrationPlan;
use crate::migration::fields::FieldDescriptor;
use crate::migration::provided::Provided;
use crate::schema::Lineage;
use crate::store::OnUndeclared;
use amethystate_core::path::StorePath;
use std::collections::HashMap;
#[derive(Default)]
pub struct MigrationSet {
migrators: HashMap<Lineage, MigrationPlan>,
targets: HashMap<Lineage, (u32, &'static [FieldDescriptor])>,
provided: Provided,
on_undeclared: OnUndeclared,
}
impl MigrationSet {
pub fn provide<T: std::any::Any>(&mut self, value: T) {
self.provided.insert(value);
}
pub(crate) fn take_provided(&mut self, provided: Provided) {
self.provided = provided;
}
pub(crate) fn provided(&self) -> &Provided {
&self.provided
}
pub(crate) fn take_on_undeclared(&mut self, rule: OnUndeclared) {
self.on_undeclared = rule;
}
pub(crate) fn on_undeclared(&self) -> OnUndeclared {
self.on_undeclared
}
pub fn add(
mut self,
lineage: impl Into<Lineage>,
migrator: MigrationPlan,
fields: &'static [FieldDescriptor],
) -> Self {
let lineage = lineage.into();
let target_version = migrator
.steps
.iter()
.map(|s| s.target_version())
.max()
.unwrap_or(0);
self.targets
.insert(lineage.clone(), (target_version, fields));
self.migrators.insert(lineage, migrator);
self
}
pub(crate) fn get_target(&self, lineage: &Lineage) -> (u32, &'static [FieldDescriptor]) {
let mut furthest = 0;
let mut fields: &'static [FieldDescriptor] = &[];
for entry in crate::schema::declarations_of(lineage) {
if entry.version >= furthest {
furthest = entry.version;
fields = entry.fields;
}
}
match self.targets.get(lineage) {
Some((planned, of_the_plan)) => {
let fields = match fields.is_empty() {
true => *of_the_plan,
false => fields,
};
(furthest.max(*planned), fields)
}
None => (furthest, fields),
}
}
pub(crate) fn known_lineages(&self) -> Vec<Lineage> {
let mut found: Vec<Lineage> = self.targets.keys().cloned().collect();
found.sort();
found
}
pub(crate) fn lineages_at(&self, prefix: &StorePath) -> Vec<Lineage> {
let mut found: Vec<Lineage> = self
.targets
.keys()
.filter(|lineage| lineage.prefix == *prefix)
.cloned()
.collect();
found.sort();
found
}
pub(crate) fn owner_of(&self, key: &StorePath) -> Option<StorePath> {
let mut owner: Option<&StorePath> = None;
for at in self.targets.keys().map(|lineage| &lineage.prefix) {
if key.starts_with(at) && owner.is_none_or(|held| held.len() < at.len()) {
owner = Some(at);
}
}
owner.cloned()
}
pub(crate) fn get_migration_plan(&self, lineage: &Lineage) -> Option<&MigrationPlan> {
self.migrators.get(lineage)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::migration::fields::FieldDescriptor;
const EMPTY_FIELDS: &[FieldDescriptor] = &[];
fn dummy_migrator() -> MigrationPlan {
MigrationPlan::new()
}
fn at(joined: &str) -> StorePath {
StorePath::parse_joined(joined).expect("a path the tests wrote themselves")
}
#[test]
fn the_lines_come_back_sorted_whatever_order_they_were_added_in() {
let one = MigrationSet::default()
.add(at("x"), dummy_migrator(), EMPTY_FIELDS)
.add(Lineage::named(at("a"), "b"), dummy_migrator(), EMPTY_FIELDS)
.add(at("a"), dummy_migrator(), EMPTY_FIELDS);
let other = MigrationSet::default()
.add(at("a"), dummy_migrator(), EMPTY_FIELDS)
.add(at("x"), dummy_migrator(), EMPTY_FIELDS)
.add(Lineage::named(at("a"), "b"), dummy_migrator(), EMPTY_FIELDS);
assert_eq!(
one.known_lineages(),
vec![
Lineage::unnamed(at("a")),
Lineage::named(at("a"), "b"),
Lineage::unnamed(at("x")),
]
);
assert_eq!(one.known_lineages(), other.known_lineages());
}
#[test]
fn two_lines_at_one_prefix_keep_their_own_steps() {
let set = MigrationSet::default()
.add(
Lineage::named(at("app"), "left"),
MigrationPlan::new().step(2, "left", |_| Ok(())),
EMPTY_FIELDS,
)
.add(
Lineage::named(at("app"), "right"),
MigrationPlan::new().step(5, "right", |_| Ok(())),
EMPTY_FIELDS,
);
assert_eq!(set.get_target(&Lineage::named(at("app"), "left")).0, 2);
assert_eq!(set.get_target(&Lineage::named(at("app"), "right")).0, 5);
assert_eq!(
set.lineages_at(&at("app")),
vec![
Lineage::named(at("app"), "left"),
Lineage::named(at("app"), "right"),
]
);
}
#[test]
fn a_key_belongs_to_the_longest_prefix_that_starts_it() {
let set = MigrationSet::default()
.add(at("app"), dummy_migrator(), EMPTY_FIELDS)
.add(at("app.ui"), dummy_migrator(), EMPTY_FIELDS);
assert_eq!(
set.owner_of(&at("app.ui.theme")),
Some(StorePath::from_segments(["app", "ui"]))
);
assert_eq!(
set.owner_of(&at("app.net")),
Some(StorePath::segment("app"))
);
}
#[test]
fn a_key_under_nothing_declared_belongs_to_nobody() {
let set = MigrationSet::default().add(at("app"), dummy_migrator(), EMPTY_FIELDS);
assert_eq!(set.owner_of(&at("other.thing")), None);
}
#[test]
fn a_prefix_is_not_the_owner_of_a_name_it_merely_starts() {
let set = MigrationSet::default().add(at("app"), dummy_migrator(), EMPTY_FIELDS);
assert_eq!(
set.owner_of(&at("application.thing")),
None,
"`app` starts the string `application` and starts none of its levels"
);
}
#[test]
fn a_name_holding_a_separator_is_one_level_and_owns_nothing_under_the_two() {
let set = MigrationSet::default().add(at("app"), dummy_migrator(), EMPTY_FIELDS);
assert_eq!(
set.owner_of(&StorePath::segment("app.ui")),
None,
"one level called `app.ui` is not a key under `app`"
);
}
#[test]
fn test_target_info_retrieval() {
static TEST_FIELDS: &[FieldDescriptor] = &[FieldDescriptor::leaf(&["id"], "id", "u64")];
let migrator = MigrationPlan::new().step(1, "init", |_| Ok(()));
let set = MigrationSet::default().add(at("app"), migrator, TEST_FIELDS);
let (v, f) = set.get_target(&at("app").into());
assert_eq!(v, 1);
assert_eq!(f.len(), 1);
assert_eq!(f[0].name.as_str(), "id");
assert_eq!(f[0].type_name, "u64");
}
}