use chrono::{DateTime, Utc};
use dpm_model::{
ActorId, LineageId, OperationId, RunEventId, RunId, RunState, ValidationError, WorkItemId,
WorkStatus,
};
use thiserror::Error;
mod link;
mod projection;
mod source;
mod start;
mod transition;
pub use link::{OperationFacts, check_link};
pub use projection::{STALE_AFTER, project};
pub use start::observe_start;
pub use transition::{authorize, check_transition};
#[derive(Debug, Error)]
pub enum RunError {
#[error(transparent)]
Validation(#[from] ValidationError),
#[error("work item {0} does not exist")]
MissingWork(WorkItemId),
#[error("work item {0} is not a task and cannot be executed")]
NotATask(WorkItemId),
#[error("work item {work} is {status:?}; a run starts only on claimed or started work")]
NotExecuting {
work: WorkItemId,
status: WorkStatus,
},
#[error("work item {work} is owned by {}, not {executor}", owner.as_ref().map_or_else(|| "no one".to_string(), ToString::to_string))]
NotOwner {
work: WorkItemId,
executor: ActorId,
owner: Option<ActorId>,
},
#[error("actor {actor} may not {action} run {run} executed by {executor}")]
ActorNotAllowed {
actor: ActorId,
action: &'static str,
run: RunId,
executor: ActorId,
},
#[error("run {0} cannot be managed: only a service actor can record a managed run")]
ManagedNeedsService(RunId),
#[error("run source is not supported by the plan: {0}")]
UnsupportedSource(String),
#[error("run {run} cannot move from {from} to {to}")]
InvalidTransition {
run: RunId,
from: RunState,
to: RunState,
},
#[error("run {run} already ended as {state}; start a new run to try again")]
Terminal {
run: RunId,
state: RunState,
},
#[error("operation {operation} cannot be linked to run {run}: {reason}")]
LinkRefused {
run: RunId,
operation: OperationId,
#[source]
reason: LinkRefusal,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum LinkRefusal {
#[error("it belongs to another workspace")]
OtherWorkspace,
#[error("it was committed under another lineage than the run observed")]
OtherLineage,
#[error("it does not act on the run's task")]
OtherWork,
#[error("it was made by someone other than the run's executor")]
OtherActor,
#[error("it was committed before the run started")]
BeforeStart,
#[error("it was committed after the run ended")]
AfterEnd,
}
#[must_use]
pub fn start_event(run: RunId) -> RunEventId {
RunEventId(run.0)
}
fn stale_after(receipt: DateTime<Utc>) -> Option<DateTime<Utc>> {
receipt.checked_add_signed(STALE_AFTER)
}
fn same_history(recorded: LineageId, current: Option<LineageId>) -> bool {
current == Some(recorded)
}
#[cfg(test)]
mod tests;