use async_trait::async_trait;
use json_patch::{Patch, PatchOperation};
use serde_json::Value;
use std::collections::hash_map::DefaultHasher;
use std::fmt::{self, Display};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{info, instrument};
use crate::system::System;
use crate::task::{Action, Error as TaskError};
mod aggregate_error;
mod channel;
mod dag;
mod interrupt;
pub use dag::*;
pub use interrupt::Interrupt;
pub(crate) use aggregate_error::*;
pub(crate) use channel::*;
pub(crate) use interrupt::*;
#[derive(Hash)]
struct WorkUnitId<'s> {
task_id: String,
path: String,
state: &'s Value,
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct WorkUnit {
pub id: u64,
action: Action,
output: Vec<PatchOperation>,
}
impl WorkUnit {
pub fn new(id: u64, action: Action, output: Vec<PatchOperation>) -> Self {
Self { id, action, output }
}
pub fn new_id(task: &Action, state: &Value) -> u64 {
let pointer = task.context().path.as_ref();
let state = pointer.resolve(state).unwrap_or(&Value::Null);
let action_id = WorkUnitId {
task_id: String::from(task.id()),
path: task.context().path.to_string(),
state,
};
let mut hasher = DefaultHasher::new();
action_id.hash(&mut hasher);
hasher.finish()
}
}
impl Display for WorkUnit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.action.fmt(f)
}
}
#[async_trait]
impl Task for WorkUnit {
type Input = System;
type Changes = Patch;
type Error = TaskError;
#[instrument(name = "run_task", skip_all, fields(task=%self.action), err)]
async fn run(&self, system: &System) -> Result<Patch, TaskError> {
info!("starting");
let Patch(changes) = self.action.dry_run(system)?;
if changes != self.output {
return Err(TaskError::ConditionFailed);
}
self.action.run(system).await
}
}
#[derive(Default, Clone)]
pub struct Workflow(pub(crate) Dag<WorkUnit>);
pub(crate) enum WorkflowStatus {
Completed,
Interrupted,
}
impl From<ExecutionStatus> for WorkflowStatus {
fn from(status: ExecutionStatus) -> WorkflowStatus {
match status {
ExecutionStatus::Completed => WorkflowStatus::Completed,
ExecutionStatus::Interrupted => WorkflowStatus::Interrupted,
}
}
}
impl Workflow {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub(crate) async fn execute(
self,
sys_reader: &Arc<RwLock<System>>,
patch_tx: Sender<Patch>,
interrupt: Interrupt,
) -> Result<WorkflowStatus, AggregateError<TaskError>> {
self.0
.execute(sys_reader, patch_tx, interrupt)
.await
.map(|s| s.into())
}
}
impl Display for Workflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}