#![deny(missing_docs)]
use uuid::Uuid;
use std::sync::Arc;
use thiserror::Error;
use async_trait::async_trait;
pub mod prelude;
mod context;
mod runtime;
pub use runtime::{Runtime, RuntimeBuilder};
#[async_trait]
pub trait Action: Send + Sync {
async fn run(&self, runtime: Runtime, operation: Operation) -> Result<Option<ActionOutput>, ActionError>;
async fn probe(&self, runtime: Runtime) -> Result<Probe, ActionError>;
async fn load_state(&self, _builder: &mut RuntimeBuilder) {}
fn display_name(&self) -> String;
}
#[derive(Clone)]
pub struct ActionObject {
action: Arc<dyn Action>,
deps: Vec<ActionObject>,
id: Id
}
impl ActionObject {
pub fn new(action: Arc<dyn Action>) -> Self {
Self {
action,
deps: Vec::new(),
id: Id::default()
}
}
pub fn display_name(&self) -> String {
self.action.display_name()
}
pub(crate) fn id(&self) -> Id {
self.id
}
pub(crate) fn deps(&self) -> Vec<ActionObject> {
self.deps.clone()
}
pub(crate) async fn probe(&self, ctx: Runtime) -> Result<Probe, ActionError> {
self.action.probe(ctx).await
}
pub(crate) async fn run(&self, ctx: Runtime, operation: Operation) -> Result<Option<ActionOutput>, ActionError> {
self.action.run(ctx, operation).await
}
pub fn requires(&mut self, action: ActionObject) {
self.deps.push(action);
}
pub async fn load_state(&self, builder: &mut RuntimeBuilder) {
self.action.load_state(builder).await;
}
}
impl<A> From<A> for ActionObject
where
A: Action + 'static
{
fn from(action: A) -> Self {
Self::new(Arc::new(action))
}
}
#[derive(Default, Clone)]
pub struct ContextCallbacks {
pub on_action_started: Option<fn(ActionObject)>,
pub on_action_finished: Option<fn(ActionObject)>,
pub on_action_failed: Option<fn(ActionObject, &ActionError)>
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Id(Uuid);
impl Default for Id {
fn default() -> Self {
Self(Uuid::new_v4())
}
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone)]
pub enum ActionOutput {
String(String),
Integer(i64),
Float(f64),
Boolean(bool)
}
impl TryFrom<ActionOutput> for String {
type Error = ActionError;
fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
match value {
ActionOutput::String(value) => Ok(value),
_ => Err(ActionError::OutputConversionFailed("String".to_string()))
}
}
}
impl TryFrom<ActionOutput> for i64 {
type Error = ActionError;
fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
match value {
ActionOutput::Integer(value) => Ok(value),
_ => Err(ActionError::OutputConversionFailed("i64".to_string()))
}
}
}
impl TryFrom<ActionOutput> for f64 {
type Error = ActionError;
fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
match value {
ActionOutput::Float(value) => Ok(value),
_ => Err(ActionError::OutputConversionFailed("f64".to_string()))
}
}
}
impl TryFrom<ActionOutput> for bool {
type Error = ActionError;
fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
match value {
ActionOutput::Boolean(value) => Ok(value),
_ => Err(ActionError::OutputConversionFailed("bool".to_string()))
}
}
}
impl From<String> for ActionOutput {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<i64> for ActionOutput {
fn from(value: i64) -> Self {
Self::Integer(value)
}
}
impl From<f64> for ActionOutput {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<bool> for ActionOutput {
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}
impl From<&str> for ActionOutput {
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
pub enum ActionInput<T> {
Static(T),
Dynamic(ActionObject)
}
impl<T> ActionInput<T> {
pub fn new_dynamic(value: ActionObject) -> Self {
Self::Dynamic(value)
}
pub fn new_static(value: T) -> Self {
Self::Static(value)
}
pub fn static_value(&self) -> Option<&T> {
match self {
Self::Static(value) => Some(value),
_ => None
}
}
pub fn dynamic(&self) -> Option<ActionObject> {
match self {
Self::Dynamic(action) => Some(action.clone()),
_ => None
}
}
pub fn is_static(&self) -> bool {
self.static_value().is_some()
}
pub fn is_dynamic(&self) -> bool {
self.dynamic().is_some()
}
pub fn unwrap_static(&self) -> &T {
self.static_value().unwrap()
}
pub fn unwrap_dynamic(&self) -> ActionObject {
self.dynamic().unwrap()
}
}
impl<T> From<T> for ActionInput<T> {
fn from(value: T) -> Self {
Self::new_static(value)
}
}
impl<T: Default> Default for ActionInput<T> {
fn default() -> Self {
Self::new_static(T::default())
}
}
#[derive(Debug, Error, Clone)]
#[non_exhaustive]
pub enum ActionError {
#[error("{0}")]
ActionFailed(String, String),
#[error("Could not convert ActionOutput to {0}")]
OutputConversionFailed(String),
#[error("An internal error occured, please report this error code: {0}")]
InternalError(&'static str),
#[error("Dependency did not return a value")]
NoActionReturn,
#[error("Operation not supported")]
OperationNotSupported,
#[error("Required state was not loaded")]
StateNotLoaded
}
pub enum Operation {
Perform,
Rollback
}
#[derive(Debug, Clone)]
pub struct Probe {
pub needs_run: bool,
pub can_rollback: bool
}
impl Default for Probe {
fn default() -> Self {
Self {
needs_run: true,
can_rollback: false
}
}
}