1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//! Describe how to execute tasks
use crate::task::Task;

/// Result of running a task.
pub enum RunTaskResult {
    /// Task ran successfully
    Finished {
        /// Serialized result
        result: String,
    },

    /// Task failed by returning an `Err` value
    Failed {
        /// Serialized `Err` value
        result: String,
    },

    /// Something could not be serialized or deserialized correctly
    SerdeErr {
        /// Error messaged provided by `serde_json`
        msg: String,
    },

    /// Job was not run (not an error)
    None,
}

impl RunTaskResult {
    pub fn is_none(&self) -> bool {
        if let RunTaskResult::None = self {
            return true;
        }
        false
    }
}

macro_rules! handle_serialize_err {
    ($payload:ident) => {
        match serde_json::to_string(&$payload) {
            Ok(result) => result,

            Err(e) => {
                return RunTaskResult::SerdeErr {
                    msg: format!("{}", e),
                }
            }
        }
    };
}

pub trait Job {
    /// Result type produced by the `run()` method.
    /// Use `()` for jobs that are only executed for their side-effects.
    type R: serde::Serialize + serde::de::DeserializeOwned;

    /// Error type produced by the `run()` method.
    /// Use `()` for jobs that always succeed.
    type E: serde::Serialize + serde::de::DeserializeOwned;

    /// Each job must have a unique UUID. If any aspect of a job definition is changed, it's a
    /// good idea to update this.
    const UUID: &'static str;

    /// Maximum number of attempts (including first run) to execute job. Jobs that panic are never retried.
    /// Reruns are scheduled to occur at fail time + 5 seconds + attempts^4 seconds.
    /// Set to 1 if retry logic isn't desired.
    const MAX_ATTEMPTS: usize;

    /// Executed when an associated task is de-queued and run
    fn run(&self) -> Result<Self::R, Self::E>;

    /// Execute associated `Task`. Not meant to be re-implemented or called manually. The only caller
    /// should be the `process_jobs!` macro.
    fn run_task(&self, task: &Task) -> RunTaskResult {
        if task.job_uuid != Self::UUID {
            return RunTaskResult::None;
        }

        match self.run() {
            Ok(result) => RunTaskResult::Finished {
                result: handle_serialize_err!(result),
            },

            Err(error) => RunTaskResult::Failed {
                result: handle_serialize_err!(error),
            },
        }
    }
}