use super::{
DataType, Database, DatabaseValue, FilterableQuery, RUNS, RepresentationError as Error,
TransactionMode, column, create_table,
};
use crate::WorkflowExecutionAuthority;
use serde::{Deserialize, Serialize};
const PAUSE: &str = "workflow_user_pause_v2";
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PauseRequest {
pub version: u32,
pub run_id: String,
pub expected_revision: u64,
pub paused: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GateRequest {
pub version: u32,
pub run_id: String,
pub node_id: String,
pub activation_id: String,
pub expected_revision: u64,
pub decision: GateDecision,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GateDecision {
Input(serde_json::Value),
Approve,
Deny,
}
pub(super) async fn install(db: &dyn Database) -> Result<(), Error> {
create_table(PAUSE)
.column(column("run_id", DataType::Text, false))
.primary_key("run_id")
.execute(db)
.await?;
Ok(())
}
pub async fn user_paused(db: &dyn Database, run: &str) -> Result<bool, Error> {
super::verified(db).await?;
Ok(!db
.select(PAUSE)
.where_eq("run_id", run)
.limit(1)
.execute(db)
.await?
.is_empty())
}
pub async fn set_pause(
db: &dyn Database,
owner: &WorkflowExecutionAuthority,
request: &PauseRequest,
) -> Result<bool, Error> {
if request.version != 1 {
return Err(Error::InvalidAdmission);
}
let tx = db
.begin_transaction_with_mode(TransactionMode::Immediate)
.await?;
super::verified(&*tx).await?;
let roots = tx
.select(RUNS)
.where_eq("run_id", request.run_id.as_str())
.limit(1)
.execute(&*tx)
.await?;
let root = roots.first().ok_or(Error::InvalidAdmission)?;
if root.get("authority_json")
!= Some(DatabaseValue::String(
serde_json::to_string(owner).map_err(|_| Error::InvalidAdmission)?,
))
{
return Err(Error::AuthorityConflict);
}
if root.get("status") != Some(DatabaseValue::String("paused".into()))
|| super::graph_pages::revision(&*tx, &request.run_id).await? != request.expected_revision
{
return Err(Error::InvalidAdmission);
}
let paused = user_paused(&*tx, &request.run_id).await?;
if request.paused && !paused {
tx.insert(PAUSE)
.value("run_id", request.run_id.as_str())
.execute(&*tx)
.await?;
}
if !request.paused && paused {
tx.delete(PAUSE)
.where_eq("run_id", request.run_id.as_str())
.execute(&*tx)
.await?;
}
tx.commit().await?;
Ok(paused != request.paused)
}
pub async fn resolve_gate(
db: &dyn Database,
owner: &WorkflowExecutionAuthority,
request: &GateRequest,
) -> Result<super::graph::gates::GateResolution, Error> {
if request.version != 1 {
return Err(Error::InvalidAdmission);
}
match &request.decision {
GateDecision::Deny => {
super::graph::gates::deny_observed(
db,
owner,
&request.run_id,
&request.node_id,
&request.activation_id,
request.expected_revision,
)
.await
}
decision => {
let input = match decision {
GateDecision::Input(value) => Some(value),
_ => None,
};
super::graph::gates::resolve_observed(
db,
owner,
&request.run_id,
&request.node_id,
&request.activation_id,
request.expected_revision,
input,
)
.await
}
}
}