hypertask_engine 0.2.0

The core logic engine used to run hypertask
Documentation
use crate::error::*;
use crate::id::Id;
use crate::prop::Prop;
use crate::rpn::StackMachine;
use crate::tag::Tag;
use crate::task::{FinalisedTask, Task};
use chrono::prelude::*;

#[derive(Debug, Deserialize, Serialize)]
pub enum Mutation {
    SetProp(Prop),
    SetTag(Tag),
}

#[derive(Debug, Deserialize, Serialize)]
pub enum Query {
    Id(Id),
    Tag(Tag),
}

#[derive(Debug, Deserialize, Serialize)]
pub enum Command {
    Create(Vec<Mutation>),
    Read(Vec<Query>),
    Update(Vec<Query>, Vec<Mutation>),
    Delete(Vec<Query>),
}

pub trait HyperTaskEngineContext<TaskIterator: Iterator<Item = HyperTaskResult<Task>>> {
    fn finalize_mutations(&self) -> HyperTaskResult<()>;
    fn generate_id(&mut self) -> String;
    fn get_now(&self) -> DateTime<Utc>;
    fn get_stack_machine(&self) -> HyperTaskResult<StackMachine>;
    fn get_task_iterator(&self) -> HyperTaskResult<TaskIterator>;
    fn put_task(&mut self, task: &Task) -> HyperTaskResult<()>;
}

struct TaskEngine<
    InputIterator: Iterator<Item = HyperTaskResult<Task>>,
    Context: HyperTaskEngineContext<InputIterator>,
> {
    command: Command,
    input_iterator: InputIterator,
    context: Context,
    now: DateTime<Utc>,
    done: bool,
}

impl<
        InputIterator: Iterator<Item = HyperTaskResult<Task>>,
        Context: HyperTaskEngineContext<InputIterator>,
    > TaskEngine<InputIterator, Context>
{
    pub fn new(command: Command, context: Context) -> HyperTaskResult<Self> {
        let now = context.get_now();
        let input_iterator = context.get_task_iterator()?;

        Ok(Self {
            command,
            input_iterator,
            context,
            now,
            done: false,
        })
    }

    fn yield_next_task(&mut self) -> Option<HyperTaskResult<Task>> {
        if self.done {
            return None;
        }

        match &self.command {
            Command::Read(queries) => match self.input_iterator.next()? {
                Err(e) => Some(Err(e)),
                Ok(next_task) => {
                    if queries.is_empty() || next_task.satisfies_queries(&queries) {
                        Some(Ok(next_task))
                    } else {
                        self.yield_next_task()
                    }
                }
            },

            Command::Create(mutations) => {
                let mut new_task = Task::generate(&mut self.context);
                new_task.apply_mutations(mutations, &self.now);

                self.done = true;

                Some(self.context.put_task(&new_task).map(|_| new_task))
            }

            Command::Update(queries, mutations) => match self.input_iterator.next()? {
                Err(e) => Some(Err(e)),
                Ok(mut next_task) => {
                    if next_task.satisfies_queries(&queries) {
                        next_task.apply_mutations(mutations, &self.now);

                        Some(self.context.put_task(&next_task).map(|_| next_task))
                    } else {
                        self.yield_next_task()
                    }
                }
            },

            Command::Delete(_) => {
                panic!("fuck you");
            }
        }
    }
}

impl<
        InputIterator: Iterator<Item = HyperTaskResult<Task>>,
        Context: HyperTaskEngineContext<InputIterator>,
    > Iterator for TaskEngine<InputIterator, Context>
{
    type Item = HyperTaskResult<Task>;

    fn next(&mut self) -> Option<Self::Item> {
        let next_task = self.yield_next_task();

        if next_task.is_some() {
            return next_task;
        }

        if let Command::Read(_) = self.command {
            return None;
        }

        match self.context.finalize_mutations() {
            Ok(_) => None,
            Err(e) => Some(Err(e)),
        }
    }
}

pub fn run<InputIterator, Context>(
    command: Command,
    context: Context,
) -> HyperTaskResult<Vec<FinalisedTask>>
where
    InputIterator: Iterator<Item = HyperTaskResult<Task>>,
    Context: HyperTaskEngineContext<InputIterator>,
{
    let mut stack_machine = context.get_stack_machine()?;

    let mut task_collection = TaskEngine::new(command, context)?
        .map(|task_result| task_result.and_then(|task| task.finalise(&mut stack_machine)))
        .filter(|finalised_task| {
            if let Ok(ft) = finalised_task {
                (ft.get_score() - 0.0).abs() > std::f64::EPSILON
            } else {
                true
            }
        })
        .collect::<HyperTaskResult<Vec<FinalisedTask>>>()?;

    task_collection.sort_unstable();

    Ok(task_collection)
}