use std::fmt;
use std::num::NonZeroU64;
use std::time::SystemTime;
use oxide_batch_core::{
DomainError, ExecutionContext, ExitCode, FlowTarget, IdentifierKind, JobExecutionId, NodeId,
StepExecution, StepExecutionId,
};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FlowDecisionId(NonZeroU64);
impl FlowDecisionId {
pub fn new(value: u64) -> Result<Self, DomainError> {
NonZeroU64::new(value)
.map(Self)
.ok_or(DomainError::ZeroIdentifier {
kind: IdentifierKind::FlowDecision,
})
}
#[must_use]
pub const fn get(self) -> u64 {
self.0.get()
}
}
impl fmt::Display for FlowDecisionId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.get().fmt(formatter)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FlowDecisionSequence(NonZeroU64);
impl FlowDecisionSequence {
pub fn new(value: u64) -> Result<Self, DomainError> {
NonZeroU64::new(value)
.map(Self)
.ok_or(DomainError::ZeroIdentifier {
kind: IdentifierKind::FlowDecisionSequence,
})
}
#[must_use]
pub const fn get(self) -> u64 {
self.0.get()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum FlowTransitionKind {
StepExit,
Decider,
CompletedStepReuse,
SplitAggregate,
}
impl FlowTransitionKind {
#[doc(hidden)]
#[must_use]
pub const fn durable_code(self) -> &'static str {
match self {
Self::StepExit => "STEP_EXIT",
Self::Decider => "DECIDER",
Self::CompletedStepReuse => "COMPLETED_STEP_REUSE",
Self::SplitAggregate => "SPLIT_AGGREGATE",
}
}
#[doc(hidden)]
#[must_use]
pub fn from_durable_code(value: &str) -> Option<Self> {
match value {
"STEP_EXIT" => Some(Self::StepExit),
"DECIDER" => Some(Self::Decider),
"COMPLETED_STEP_REUSE" => Some(Self::CompletedStepReuse),
"SPLIT_AGGREGATE" => Some(Self::SplitAggregate),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FlowDecision {
id: FlowDecisionId,
job_execution_id: JobExecutionId,
sequence: FlowDecisionSequence,
source_node_id: NodeId,
source_step_execution_id: Option<StepExecutionId>,
kind: FlowTransitionKind,
observed_outcome: ExitCode,
target: FlowTarget,
plan_fingerprint: [u8; 32],
input_digest: [u8; 32],
reused_decision_id: Option<FlowDecisionId>,
decided_at: SystemTime,
}
impl FlowDecision {
#[allow(clippy::too_many_arguments)]
#[doc(hidden)]
#[must_use]
pub const fn new(
id: FlowDecisionId,
job_execution_id: JobExecutionId,
sequence: FlowDecisionSequence,
source_node_id: NodeId,
source_step_execution_id: Option<StepExecutionId>,
kind: FlowTransitionKind,
observed_outcome: ExitCode,
target: FlowTarget,
plan_fingerprint: [u8; 32],
input_digest: [u8; 32],
reused_decision_id: Option<FlowDecisionId>,
decided_at: SystemTime,
) -> Self {
Self {
id,
job_execution_id,
sequence,
source_node_id,
source_step_execution_id,
kind,
observed_outcome,
target,
plan_fingerprint,
input_digest,
reused_decision_id,
decided_at,
}
}
#[must_use]
pub const fn id(&self) -> FlowDecisionId {
self.id
}
#[must_use]
pub const fn job_execution_id(&self) -> JobExecutionId {
self.job_execution_id
}
#[must_use]
pub const fn sequence(&self) -> FlowDecisionSequence {
self.sequence
}
#[must_use]
pub const fn source_node_id(&self) -> &NodeId {
&self.source_node_id
}
#[must_use]
pub const fn source_step_execution_id(&self) -> Option<StepExecutionId> {
self.source_step_execution_id
}
#[must_use]
pub const fn kind(&self) -> FlowTransitionKind {
self.kind
}
#[must_use]
pub const fn observed_outcome(&self) -> &ExitCode {
&self.observed_outcome
}
#[must_use]
pub const fn target(&self) -> &FlowTarget {
&self.target
}
#[must_use]
pub const fn plan_fingerprint(&self) -> &[u8; 32] {
&self.plan_fingerprint
}
#[must_use]
pub const fn input_digest(&self) -> &[u8; 32] {
&self.input_digest
}
#[must_use]
pub const fn reused_decision_id(&self) -> Option<FlowDecisionId> {
self.reused_decision_id
}
#[must_use]
pub const fn decided_at(&self) -> SystemTime {
self.decided_at
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FlowDecisionRequest {
job_execution_id: JobExecutionId,
sequence: FlowDecisionSequence,
source_node_id: NodeId,
source_step_execution_id: Option<StepExecutionId>,
kind: FlowTransitionKind,
observed_outcome: ExitCode,
target: FlowTarget,
plan_fingerprint: [u8; 32],
input_digest: [u8; 32],
reused_decision_id: Option<FlowDecisionId>,
decided_at: SystemTime,
}
impl FlowDecisionRequest {
#[allow(clippy::too_many_arguments)]
#[doc(hidden)]
#[must_use]
pub const fn new(
job_execution_id: JobExecutionId,
sequence: FlowDecisionSequence,
source_node_id: NodeId,
source_step_execution_id: Option<StepExecutionId>,
kind: FlowTransitionKind,
observed_outcome: ExitCode,
target: FlowTarget,
plan_fingerprint: [u8; 32],
input_digest: [u8; 32],
reused_decision_id: Option<FlowDecisionId>,
decided_at: SystemTime,
) -> Self {
Self {
job_execution_id,
sequence,
source_node_id,
source_step_execution_id,
kind,
observed_outcome,
target,
plan_fingerprint,
input_digest,
reused_decision_id,
decided_at,
}
}
#[must_use]
pub const fn job_execution_id(&self) -> JobExecutionId {
self.job_execution_id
}
#[must_use]
pub const fn sequence(&self) -> FlowDecisionSequence {
self.sequence
}
#[must_use]
pub const fn source_node_id(&self) -> &NodeId {
&self.source_node_id
}
#[must_use]
pub const fn source_step_execution_id(&self) -> Option<StepExecutionId> {
self.source_step_execution_id
}
#[must_use]
pub const fn kind(&self) -> FlowTransitionKind {
self.kind
}
#[must_use]
pub const fn observed_outcome(&self) -> &ExitCode {
&self.observed_outcome
}
#[must_use]
pub const fn target(&self) -> &FlowTarget {
&self.target
}
#[must_use]
pub const fn plan_fingerprint(&self) -> &[u8; 32] {
&self.plan_fingerprint
}
#[must_use]
pub const fn input_digest(&self) -> &[u8; 32] {
&self.input_digest
}
#[must_use]
pub const fn reused_decision_id(&self) -> Option<FlowDecisionId> {
self.reused_decision_id
}
#[must_use]
pub const fn decided_at(&self) -> SystemTime {
self.decided_at
}
#[must_use]
pub fn materialize(&self, id: FlowDecisionId) -> FlowDecision {
FlowDecision::new(
id,
self.job_execution_id,
self.sequence,
self.source_node_id.clone(),
self.source_step_execution_id,
self.kind,
self.observed_outcome.clone(),
self.target.clone(),
self.plan_fingerprint,
self.input_digest,
self.reused_decision_id,
self.decided_at,
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FlowStepState {
node_id: NodeId,
execution: StepExecution,
context: Option<ExecutionContext>,
}
impl FlowStepState {
#[must_use]
pub const fn new(
node_id: NodeId,
execution: StepExecution,
context: Option<ExecutionContext>,
) -> Self {
Self {
node_id,
execution,
context,
}
}
#[must_use]
pub const fn node_id(&self) -> &NodeId {
&self.node_id
}
#[must_use]
pub const fn execution(&self) -> &StepExecution {
&self.execution
}
#[must_use]
pub const fn context(&self) -> Option<&ExecutionContext> {
self.context.as_ref()
}
}