use crate::{ProviderError, ProviderResult};
use appcore_contracts::{CapabilityId, CoreId, JobId};
#[derive(Clone, PartialEq, Eq)]
pub struct JobSpec {
job_id: JobId,
capability: CapabilityId,
payload_reference: String,
available_at_ms: u64,
max_attempts: u32,
}
impl std::fmt::Debug for JobSpec {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("JobSpec")
.field("job_id", &self.job_id)
.field("capability", &self.capability)
.field("payload_reference", &"REDACTED")
.field("available_at_ms", &self.available_at_ms)
.field("max_attempts", &self.max_attempts)
.finish()
}
}
impl JobSpec {
pub fn new(
job_id: JobId,
capability: CapabilityId,
payload_reference: impl Into<String>,
available_at_ms: u64,
max_attempts: u32,
) -> ProviderResult<Self> {
let payload_reference = payload_reference.into();
validate_payload_reference(&payload_reference)?;
if max_attempts == 0 {
return Err(ProviderError::InvalidConfiguration(
"job max_attempts must be greater than zero".to_string(),
));
}
Ok(Self {
job_id,
capability,
payload_reference,
available_at_ms,
max_attempts,
})
}
pub fn job_id(&self) -> &JobId {
&self.job_id
}
pub fn capability(&self) -> &CapabilityId {
&self.capability
}
pub fn payload_reference(&self) -> &str {
&self.payload_reference
}
pub fn available_at_ms(&self) -> u64 {
self.available_at_ms
}
pub fn max_attempts(&self) -> u32 {
self.max_attempts
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JobLease {
job_id: JobId,
holder_core_id: CoreId,
epoch: u64,
expires_at_ms: u64,
}
impl JobLease {
pub fn new(
job_id: JobId,
holder_core_id: CoreId,
epoch: u64,
expires_at_ms: u64,
) -> ProviderResult<Self> {
if epoch == 0 || expires_at_ms == 0 {
return Err(ProviderError::InvalidConfiguration(
"job lease epoch and expiration must be greater than zero".to_string(),
));
}
Ok(Self {
job_id,
holder_core_id,
epoch,
expires_at_ms,
})
}
pub fn job_id(&self) -> &JobId {
&self.job_id
}
pub fn holder_core_id(&self) -> &CoreId {
&self.holder_core_id
}
pub fn epoch(&self) -> u64 {
self.epoch
}
pub fn expires_at_ms(&self) -> u64 {
self.expires_at_ms
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JobCompletion {
Completed,
Failed,
RetryAt(u64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JobAtomicity {
FencedCompareAndSwap,
}
pub trait JobProvider: Send + Sync {
fn atomicity(&self) -> JobAtomicity {
JobAtomicity::FencedCompareAndSwap
}
fn submit(&self, job: JobSpec) -> ProviderResult<()>;
fn claim(
&self,
capability: &CapabilityId,
holder_core_id: &CoreId,
now_ms: u64,
lease_duration_ms: u64,
) -> ProviderResult<Option<JobLease>>;
fn complete(&self, lease: &JobLease, completion: JobCompletion) -> ProviderResult<()>;
}
fn validate_payload_reference(reference: &str) -> ProviderResult<()> {
if reference.trim().is_empty()
|| reference.len() > 2_048
|| reference.chars().any(char::is_control)
{
return Err(ProviderError::InvalidConfiguration(
"job payload reference is invalid".to_string(),
));
}
Ok(())
}