use std::{collections::HashMap, time::Duration};
use crate::error::MoveToFinishedErr;
use chrono::{DateTime, Utc};
use serde::Serialize;
use thiserror::Error;
use crate::{
luacommands::{InvokeLuaScript, MOVE_TO_FINISHED},
queue::QueueName,
};
pub struct MoveToFinished<'a, R> {
pub queue: &'a QueueName,
pub job_id: &'a str,
pub timestamp: DateTime<Utc>,
pub result: Result<&'a R, &'a str>,
pub options: MoveToFinishedOptions,
pub job_fields: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum MoveToFinishedTarget {
#[serde(rename = "completed")]
Completed,
#[serde(rename = "failed")]
Failed,
}
impl MoveToFinishedTarget {
fn as_str(&self) -> &'static str {
match self {
MoveToFinishedTarget::Completed => "completed",
MoveToFinishedTarget::Failed => "failed",
}
}
}
#[derive(Debug, Serialize)]
pub struct MoveToFinishedOptions {
#[serde(rename = "token")]
pub lock_token: String,
#[serde(rename = "keepJobs")]
pub keep_jobs: KeepJobsConfig,
#[serde(with = "crate::milliserde::duration_millis", rename = "lockDuration")]
pub lock_duration: Duration,
pub attempts: usize,
#[serde(rename = "maxMetricsSize")]
pub max_metrics_size: usize,
#[serde(rename = "fpof")]
pub fail_parent_on_fail: Option<bool>,
#[serde(rename = "cpof")]
pub continue_parent_on_failure: Option<bool>,
#[serde(rename = "idof")]
pub ignore_dependency_on_fail: Option<bool>,
#[serde(rename = "rdof")]
pub remove_dependency_on_fail: Option<bool>,
#[serde(rename = "name")]
pub worker_name: String,
pub limiter: Option<RateLimiter>,
}
#[derive(Debug, Serialize)]
pub struct KeepJobsConfig {
pub count: i64,
#[serde(with = "crate::milliserde::duration_millis_option")]
pub age: Option<Duration>,
}
#[derive(Debug, Serialize)]
pub struct RateLimiter {
pub max: usize,
#[serde(with = "crate::milliserde::duration_millis")]
pub duration: Duration,
}
#[derive(Debug, Error)]
pub enum MoveToFinishedErrXXXXXXXXX {
#[error("job has not been found")]
MissingKey,
#[error("job lock doesn't exist (anymore?)")]
MissingLock,
#[error("job was not in the active set")]
JobNotActive,
#[error("job has pending children")]
JobHasPendingChildren,
#[error("job lock is owned by other worker")]
LockNotOwned,
#[error("job has failed children")]
JobHasFailedChildren,
#[error("lua script returned unexpected value: {0}")]
UnexpectedLuaExitCode(i64),
#[error("failed to serialize job result: {0:?}")]
Serialize(#[from] serde_json::Error),
#[error("something went wrong with redis: {0:?}")]
RedisError(#[from] redis::RedisError),
}
impl<'a, R> InvokeLuaScript for MoveToFinished<'a, R>
where
R: Serialize,
{
type DomainOk = ();
type DomainErr = MoveToFinishedErr;
type RedisOutput = i64;
fn generate_invocation(&self) -> Result<redis::ScriptInvocation<'static>, Self::DomainErr> {
let job_fields_bytes = if let Some(jf) = &self.job_fields {
rmp_serde::to_vec_named(&jf).expect("rmp serde string hashmap should always succeed")
} else {
Vec::new()
};
let target = if self.result.is_ok() {
"completed"
} else {
"failed"
};
let mut invocation = MOVE_TO_FINISHED.prepare_invoke();
invocation
.key(self.queue.wait())
.key(self.queue.active())
.key(self.queue.prioritized())
.key(self.queue.events())
.key(self.queue.stalled())
.key(self.queue.limiter())
.key(self.queue.delayed())
.key(self.queue.paused())
.key(self.queue.meta())
.key(self.queue.priority_counter())
.key(if self.result.is_ok() {
self.queue.completed()
} else {
self.queue.failed()
})
.key(self.queue.job(self.job_id))
.key(self.queue.metrics())
.key(self.queue.marker())
.arg(self.job_id)
.arg(self.timestamp.timestamp_millis())
.arg(if self.result.is_ok() {
"returnvalue"
} else {
"failedReason"
})
.arg(match self.result {
Err(e) => e.to_string(),
Ok(v) => serde_json::to_string(&v)?,
})
.arg(target)
.arg("0") .arg(self.queue.prefix())
.arg(
rmp_serde::to_vec_named(&self.options)
.expect("Should always be able so serialize job options"),
)
.arg(job_fields_bytes);
Ok(invocation)
}
fn map_redis_error(&self, error: redis::RedisError) -> Self::DomainErr {
error.into()
}
fn map_value(&self, value: Self::RedisOutput) -> Result<Self::DomainOk, Self::DomainErr> {
match value {
0 => Ok(()),
-1 => Err(MoveToFinishedErr::MissingKey),
-2 => Err(MoveToFinishedErr::MissingLock),
-3 => Err(MoveToFinishedErr::JobNotActive),
-4 => Err(MoveToFinishedErr::JobHasPendingChildren),
-6 => Err(MoveToFinishedErr::LockNotOwned),
-9 => Err(MoveToFinishedErr::JobHasFailedChildren),
v => panic!("Unexpected lua exit code: {v:#?}"),
}
}
}