use std::{borrow::Cow, future::Future, marker::PhantomData, sync::Arc};
use k8s_openapi::jiff::Timestamp;
use kube::{Client, core::object::HasStatus};
pub struct RunContext<R> {
pub resource: Arc<R>,
pub client: Client,
pub run_start: Timestamp,
}
#[expect(type_alias_bounds)]
pub type MutableStepResult<R: HasStatus, D, E> = Result<MutableStepOutcome<<R as HasStatus>::Status, D>, E>;
pub type StepResult<D, E> = Result<StepOutcome<D>, E>;
pub trait StepMode<R: HasStatus, D>: Send + Sync + 'static {
type Outcome: Send + 'static;
fn into_mutable_outcome(outcome: Self::Outcome) -> MutableStepOutcome<R::Status, D>;
}
pub struct Immutable;
impl<R, D> StepMode<R, D> for Immutable
where
R: HasStatus,
D: Send + 'static,
{
type Outcome = StepOutcome<D>;
fn into_mutable_outcome(outcome: Self::Outcome) -> MutableStepOutcome<R::Status, D> {
match outcome {
StepOutcome::Stop => MutableStepOutcome::Stop,
StepOutcome::NoModification { data } => MutableStepOutcome::NoModification { data },
}
}
}
pub struct Mutable;
impl<R, D> StepMode<R, D> for Mutable
where
R: HasStatus<Status: Send + 'static>,
D: Send + 'static,
{
type Outcome = MutableStepOutcome<R::Status, D>;
fn into_mutable_outcome(outcome: Self::Outcome) -> MutableStepOutcome<R::Status, D> { outcome }
}
pub trait Step {
type Mode: StepMode<Self::Resource, Self::OutData>;
type InData: Send + 'static;
type OutData: Send + 'static;
type Resource: HasStatus;
type Error: Send + 'static;
#[expect(clippy::type_complexity)]
fn run<'a>(
&'a self,
context: &'a RunContext<Self::Resource>,
data: Self::InData,
) -> impl Future<Output = Result<<Self::Mode as StepMode<Self::Resource, Self::OutData>>::Outcome, Self::Error>>
+ Send
+ 'a;
fn traced_name(&self) -> Cow<'static, str> { Cow::Borrowed(std::any::type_name::<Self>()) }
}
pub trait ImmutableStep: Step<Mode = Immutable> {}
impl<S> ImmutableStep for S where S: Step<Mode = Immutable> {}
pub trait MutableStep: Step<Mode = Mutable> {}
impl<S> MutableStep for S where S: Step<Mode = Mutable> {}
type StepFnMarker<R, InData, OutData, Error, Mode> = fn() -> (R, InData, OutData, Error, Mode);
fn owned_context<R>(context: &RunContext<R>) -> RunContext<R> {
RunContext {
resource: context.resource.clone(),
client: context.client.clone(),
run_start: context.run_start,
}
}
pub struct StepFn<R, InData, OutData, Error, Mode, F> {
name: Cow<'static, str>,
f: F,
_marker: PhantomData<StepFnMarker<R, InData, OutData, Error, Mode>>,
}
pub fn immutable_step<R, InData, OutData, Error, F, Fut>(
name: impl Into<Cow<'static, str>>,
f: F,
) -> StepFn<R, InData, OutData, Error, Immutable, F>
where
R: HasStatus,
InData: Send + 'static,
OutData: Send + 'static,
Error: Send + 'static,
F: Fn(RunContext<R>, InData) -> Fut + Send + Sync + 'static,
Fut: Future<Output = StepResult<OutData, Error>> + Send + 'static,
{
StepFn {
name: name.into(),
f,
_marker: PhantomData,
}
}
pub fn mutable_step<R, InData, OutData, Error, F, Fut>(
name: impl Into<Cow<'static, str>>,
f: F,
) -> StepFn<R, InData, OutData, Error, Mutable, F>
where
R: HasStatus<Status: Send + 'static>,
InData: Send + 'static,
OutData: Send + 'static,
Error: Send + 'static,
F: Fn(RunContext<R>, InData) -> Fut + Send + Sync + 'static,
Fut: Future<Output = MutableStepResult<R, OutData, Error>> + Send + 'static,
{
StepFn {
name: name.into(),
f,
_marker: PhantomData,
}
}
impl<R, InData, OutData, Error, F, Fut> Step for StepFn<R, InData, OutData, Error, Immutable, F>
where
R: HasStatus,
InData: Send + 'static,
OutData: Send + 'static,
Error: Send + 'static,
F: Fn(RunContext<R>, InData) -> Fut + Send + Sync + 'static,
Fut: Future<Output = StepResult<OutData, Error>> + Send + 'static,
{
type Error = Error;
type InData = InData;
type Mode = Immutable;
type OutData = OutData;
type Resource = R;
fn run<'a>(
&'a self,
context: &'a RunContext<Self::Resource>,
data: Self::InData,
) -> impl Future<Output = StepResult<Self::OutData, Self::Error>> + Send + 'a {
let context = owned_context(context);
(self.f)(context, data)
}
fn traced_name(&self) -> Cow<'static, str> { self.name.clone() }
}
impl<R, InData, OutData, Error, F, Fut> Step for StepFn<R, InData, OutData, Error, Mutable, F>
where
R: HasStatus<Status: Send + 'static>,
InData: Send + 'static,
OutData: Send + 'static,
Error: Send + 'static,
F: Fn(RunContext<R>, InData) -> Fut + Send + Sync + 'static,
Fut: Future<Output = MutableStepResult<R, OutData, Error>> + Send + 'static,
{
type Error = Error;
type InData = InData;
type Mode = Mutable;
type OutData = OutData;
type Resource = R;
fn run<'a>(
&'a self,
context: &'a RunContext<Self::Resource>,
data: Self::InData,
) -> impl Future<Output = MutableStepResult<Self::Resource, Self::OutData, Self::Error>> + Send + 'a {
let context = owned_context(context);
(self.f)(context, data)
}
fn traced_name(&self) -> Cow<'static, str> { self.name.clone() }
}
pub enum StepOutcome<D> {
Stop,
NoModification { data: D },
}
pub enum MutableStepOutcome<S, D> {
Stop,
Modified {
status: Option<S>,
},
NoModification { data: D },
}