#[macro_use]
extern crate log;
use std::collections::{BTreeMap, BTreeSet};
use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter};
pub type Version = i64;
pub trait Migration {
fn version(&self) -> Version;
fn description(&self) -> String;
}
#[derive(Debug)]
pub enum Direction {
Down,
Up,
}
#[derive(Debug)]
pub enum Error<E> {
Adapter(E),
Migration {
version: Version,
description: String,
direction: Direction,
error: E,
}
}
impl<E: StdError> StdError for Error<E> {
fn description(&self) -> &str {
match *self {
Error::Adapter(ref err) => err.description(),
Error::Migration { ref error, .. } => error.description(),
}
}
fn cause(&self) -> Option<&StdError> {
match *self {
Error::Adapter(ref err) => Some(err),
Error::Migration { ref error, .. } => Some(error),
}
}
}
impl<E: StdError> Display for Error<E> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
Error::Adapter(ref err) => write!(f, "Adataper error: {}", err),
Error::Migration { ref description, ref error, .. } => {
write!(f, "Error running migration {}, error: {}", description, error)
},
}
}
}
#[macro_export]
macro_rules! migration {
($ty:ident, $version:expr, $description:expr) => {
impl $crate::Migration for $ty {
fn version(&self) -> $crate::Version { $version }
fn description(&self) -> String { $description.into() }
}
}
}
pub trait Adapter {
type MigrationType: Migration + ?Sized;
type Error;
fn current_version(&self) -> Result<Option<Version>, Self::Error>;
fn migrated_versions(&self) -> Result<BTreeSet<Version>, Self::Error>;
fn apply_migration(&self, migration: &Self::MigrationType) -> Result<(), Self::Error>;
fn revert_migration(&self, migration: &Self::MigrationType) -> Result<(), Self::Error>;
}
pub struct Migrator<T: Adapter> {
adapter: T,
migrations: BTreeMap<Version, Box<T::MigrationType>>
}
impl<T: Adapter> Migrator<T> {
pub fn new(adapter: T) -> Migrator<T> {
Migrator { adapter: adapter, migrations: BTreeMap::new() }
}
pub fn adapter(&self) -> &T {
&self.adapter
}
pub fn register(&mut self, migration: Box<T::MigrationType>) {
let version = migration.version();
if self.version_registered(version) {
warn!("Migration with version {:?} is already registered", version);
} else {
self.migrations.insert(version, migration);
}
}
pub fn version_registered(&self, version: Version) -> bool {
self.migrations.contains_key(&version)
}
pub fn registered_versions(&self) -> BTreeSet<Version> {
self.migrations.keys().cloned().collect()
}
pub fn first_version(&self) -> Option<Version> {
self.migrations.keys().next().map(|v| *v)
}
pub fn last_version(&self) -> Option<Version> {
self.migrations.keys().last().map(|v| *v)
}
pub fn current_version(&self) -> Result<Option<Version>, Error<T::Error>> {
match self.adapter.current_version() {
Ok(ver) => Ok(ver),
Err(err) => Err(Error::Adapter(err)),
}
}
pub fn migrated_versions(&self) -> Result<BTreeSet<Version>, Error<T::Error>> {
match self.adapter.migrated_versions() {
Ok(vers) => Ok(vers),
Err(err) => Err(Error::Adapter(err)),
}
}
pub fn down(&self, to: Option<Version>) -> Result<(), Error<T::Error>> {
let from = try!(self.current_version());
if from.is_none() {
return Ok(());
}
let migrated_versions = try!(self.migrated_versions());
let targets = self.migrations.iter()
.rev()
.filter(|&(&v, _)| within_range(v, to, from))
.filter(|&(v, _)| migrated_versions.contains(v));
for (&version, migration) in targets {
let description = migration.description();
info!("Reverting migration {:?}: {}", version, description);
if let Err(err) = self.adapter.revert_migration(migration) {
return Err(Error::Migration {
version: version,
description: description,
direction: Direction::Down,
error: err,
});
}
}
Ok(())
}
pub fn up(&self, to: Option<Version>) -> Result<(), Error<T::Error>> {
let migrated_versions = try!(self.migrated_versions());
let targets = self.migrations.iter()
.filter(|&(&v, _)| within_range(v, None, to))
.filter(|&(v, _)| !migrated_versions.contains(v));
for (&version, migration) in targets {
let description = migration.description();
info!("Applying migration {:?}: {}", version, description);
if let Err(err) = self.adapter.apply_migration(migration) {
return Err(Error::Migration {
version: version,
description: description,
direction: Direction::Up,
error: err,
});
}
}
Ok(())
}
}
fn within_range(version: Version, low: Option<Version>, high: Option<Version>) -> bool {
match (low, high) {
(None, None) => true,
(Some(low), None) => version > low,
(None, Some(high)) => version <= high,
(Some(low), Some(high)) => version > low && version <= high,
}
}
#[test]
fn test_within_range() {
assert!(within_range(0, None, None));
assert!(within_range(42, None, None));
assert!(within_range(100000, None, None));
assert!(!within_range(1, Some(2), Some(5)));
assert!(!within_range(2, Some(2), Some(5)));
assert!(within_range(3, Some(2), Some(5)));
assert!(within_range(5, Some(2), Some(5)));
assert!(!within_range(6, Some(2), Some(5)));
assert!(!within_range(0, Some(5), None));
assert!(!within_range(4, Some(5), None));
assert!(!within_range(5, Some(5), None));
assert!(within_range(6, Some(5), None));
assert!(within_range(60, Some(5), None));
assert!(within_range(0, None, Some(5)));
assert!(within_range(5, None, Some(5)));
assert!(!within_range(6, None, Some(5)));
}