Skip to content

Instantly share code, notes, and snippets.

@itarato
Last active June 14, 2020 02:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itarato/6968eb972b36ed0a8ec3fdd7076b6901 to your computer and use it in GitHub Desktop.
Save itarato/6968eb972b36ed0a8ec3fdd7076b6901 to your computer and use it in GitHub Desktop.
Feature migration framework brainstorm
use std::cell::{RefCell};
/****************************************
* Abstractions
*/
trait Task {
fn get_name(&self) -> String;
fn get_description(&self) -> String;
fn is_complete(&self) -> bool;
fn can_autocomplete(&self) -> bool;
fn autocomplete(&mut self) -> Result<(), String>;
fn progress(&self) -> f64;
}
trait Migration {
fn get_name(&self) -> String;
fn get_description(&self) -> String;
fn progress(&self) -> f64 { self.tasks().iter().map(|t| t.borrow().progress()).sum::<f64>() / (self.tasks().len() as f64) }
fn tasks(&self) -> &Vec<RefCell<Box<dyn Task>>>;
fn is_complete(&self) -> bool {
self.tasks().iter().all(|task| task.borrow().is_complete())
}
}
/****************************************
* Concrete impl
*/
struct GenericMigration {
tasks: Vec<RefCell<Box<dyn Task>>>,
name: String,
desc: String,
}
impl GenericMigration {
fn new(name: String, desc: String) -> Self {
Self {
name, desc, tasks: Default::default()
}
}
fn add_task(&mut self, task: Box<dyn Task>) {
self.tasks.push(RefCell::new(task));
}
}
impl Migration for GenericMigration {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_description(&self) -> String {
self.desc.clone()
}
fn tasks(&self) -> &Vec<RefCell<Box<dyn Task>>> {
&self.tasks
}
}
struct AcknowledgableTask {
name: String,
desc: String,
completed: bool,
}
impl AcknowledgableTask {
fn new(name: String, desc: String) -> Self {
Self {
name, desc, completed: false
}
}
}
impl Task for AcknowledgableTask {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_description(&self) -> String {
self.desc.clone()
}
fn is_complete(&self) -> bool {
self.completed
}
fn can_autocomplete(&self) -> bool {
true
}
fn autocomplete(&mut self) -> Result<(), String> {
self.completed = true;
Ok(())
}
fn progress(&self) -> f64 {
if self.completed {
1.0
} else {
0.0
}
}
}
trait Job {
fn run(&mut self);
fn progress(&self) -> f64;
fn is_complete(&self) -> bool;
}
struct DummyJob;
impl Job for DummyJob {
fn run(&mut self) {}
fn progress(&self) -> f64 { 1.0 }
fn is_complete(&self) -> bool { true }
}
struct JobTask {
name: String,
desc: String,
job: RefCell<Box<dyn Job>>,
}
impl JobTask {
fn new(name: String, desc: String, job: Box<dyn Job>) -> Self {
Self {
name, desc, job: RefCell::new(job)
}
}
}
impl Task for JobTask {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_description(&self) -> String {
self.desc.clone()
}
fn is_complete(&self) -> bool {
self.job.borrow().is_complete()
}
fn can_autocomplete(&self) -> bool {
true
}
fn autocomplete(&mut self) -> Result<(), String> {
self.job.borrow_mut().run();
Ok(())
}
fn progress(&self) -> f64 {
self.job.borrow().progress()
}
}
struct ChoiceTask {
name: String,
desc: String,
choices: Vec<RefCell<Box<dyn Task>>>,
pick: Option<usize>,
}
impl ChoiceTask {
fn new(name: String, desc: String, choices: Vec<RefCell<Box<dyn Task>>>) -> Self {
Self { name, desc, choices, pick: None }
}
fn pick(&mut self, nth: usize) -> Option<&mut RefCell<Box<dyn Task>>> {
if self.choices.len() < nth {
return None;
}
self.pick = Some(nth);
self.choices.iter_mut().nth(nth)
}
}
impl Task for ChoiceTask {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_description(&self) -> String {
self.desc.clone()
}
fn is_complete(&self) -> bool {
self.choices.iter().any(|choice| choice.borrow().is_complete())
}
fn can_autocomplete(&self) -> bool {
false
}
fn autocomplete(&mut self) -> Result<(), String> {
Err("Cannot autocomplete a choice".into())
}
fn progress(&self) -> f64 {
if self.pick.is_none() {
0.0
} else {
self.choices[self.pick.unwrap()].borrow().progress()
}
}
}
/****************************************
* Main
*/
fn main() {
let mut gm = GenericMigration::new("Example basic migration".into(), "Simple things to enable".into());
let at = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into());
gm.add_task(Box::new(at));
let jt = JobTask::new("Migration task".into(), "Running a database migration".into(), Box::new(DummyJob{}));
gm.add_task(Box::new(jt));
for task in gm.tasks() {
if task.borrow().can_autocomplete() {
let _ = task.borrow_mut().autocomplete();
}
}
let ct1 = AcknowledgableTask::new("OptionA".into(), "Simple thing".into());
let ct2 = AcknowledgableTask::new("OptionA".into(), "Simple thing".into());
let mut ct = ChoiceTask::new("Choice task".into(), "Pick one of the 2 options".into(), vec![
RefCell::new(Box::new(ct1)),
RefCell::new(Box::new(ct2)),
]);
let picked = ct.pick(1).unwrap();
if picked.borrow().can_autocomplete() {
let _ = picked.borrow_mut().autocomplete();
}
}
/****************************************
* TEST: Migration
*/
#[cfg(test)]
mod migration_tests {
use super::*;
#[test]
fn test_get_name_and_description() {
let m = GenericMigration::new("MName".into(), "MDesc".into());
assert_eq!("MName".to_owned(), m.get_name());
assert_eq!("MDesc".to_owned(), m.get_description());
}
#[test]
fn test_is_complete() {
let m = GenericMigration::new("MName".into(), "MDesc".into());
assert!(m.is_complete());
}
#[test]
fn test_is_complete_with_task() {
let mut m = GenericMigration::new("MName".into(), "MDesc".into());
let at = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into());
m.add_task(Box::new(at));
assert!(!m.is_complete());
let _ = m.tasks()[0].borrow_mut().autocomplete();
assert!(m.is_complete());
}
#[test]
fn test_progress() {
let mut m = GenericMigration::new("MName".into(), "MDesc".into());
let at1 = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into());
let at2 = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into());
m.add_task(Box::new(at1));
m.add_task(Box::new(at2));
assert_eq!(0.0f64, m.progress());
let _ = m.tasks()[0].borrow_mut().autocomplete();
assert_eq!(0.5f64, m.progress());
let _ = m.tasks()[1].borrow_mut().autocomplete();
assert_eq!(1.0f64, m.progress());
}
#[test]
fn test_tasks() {
let mut m = GenericMigration::new("MName".into(), "MDesc".into());
let at1 = AcknowledgableTask::new("Name1".into(), "Desc1".into());
let at2 = AcknowledgableTask::new("Name2".into(), "Desc2".into());
m.add_task(Box::new(at1));
m.add_task(Box::new(at2));
assert_eq!(2, m.tasks().len());
assert_eq!("Name1".to_owned(), m.tasks()[0].borrow().get_name());
assert_eq!("Name2".to_owned(), m.tasks()[1].borrow().get_name());
}
}
/****************************************
* TEST: Task
*/
#[cfg(test)]
mod task_tests {
use super::*;
#[test]
fn test_autocomplete() {
let mut t = AcknowledgableTask::new("Name1".into(), "Desc1".into());
assert!(!t.is_complete());
assert!(t.autocomplete().is_ok());
assert!(t.is_complete());
}
#[test]
fn test_can_autocomplete() {
let t = AcknowledgableTask::new("Name1".into(), "Desc1".into());
assert!(t.can_autocomplete());
let t = ChoiceTask::new("Name".into(), "Desc".into(), vec![]);
assert!(!t.can_autocomplete());
}
#[test]
fn test_get_desc() {
let t = AcknowledgableTask::new("Name1".into(), "Desc1".into());
assert_eq!("Desc1".to_owned(), t.get_description());
}
#[test]
fn test_get_name() {
let t = AcknowledgableTask::new("Name1".into(), "Desc1".into());
assert_eq!("Name1".to_owned(), t.get_name());
}
#[test]
fn test_is_complete() {
let mut t = AcknowledgableTask::new("Name1".into(), "Desc1".into());
assert!(!t.is_complete());
assert!(t.autocomplete().is_ok());
assert!(t.is_complete());
}
#[test]
fn test_progress() {
let mut t = AcknowledgableTask::new("Name1".into(), "Desc1".into());
assert_eq!(0.0f64, t.progress());
assert!(t.autocomplete().is_ok());
assert_eq!(1.0f64, t.progress());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment