json_job_dispatch/
handler.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use std::error::Error;
8
9use async_trait::async_trait;
10use serde_json::Value;
11
12use crate::director::Director;
13
14/// Results from an event.
15#[derive(Debug)]
16pub enum JobResult {
17    /// The event was accepted and acted upon.
18    Accept,
19    /// The event was deferred until a later time for the given reason.
20    Defer(String),
21    /// The event was rejected for the given reason.
22    Reject(String),
23    /// The event failed with the given error.
24    Fail(Box<dyn Error + Send + 'static>),
25    /// The director should be restarted.
26    Restart,
27    /// The event was the last one which should be processed.
28    Done,
29}
30
31impl JobResult {
32    /// Create an accept result.
33    pub fn accept() -> Self {
34        Self::Accept
35    }
36
37    /// Create a deferral result.
38    pub fn defer<M>(msg: M) -> Self
39    where
40        M: Into<String>,
41    {
42        Self::Defer(msg.into())
43    }
44
45    /// Create a rejecting result.
46    pub fn reject<M>(msg: M) -> Self
47    where
48        M: Into<String>,
49    {
50        Self::Reject(msg.into())
51    }
52
53    /// Create a failure.
54    pub fn fail<E>(err: E) -> Self
55    where
56        E: Into<Box<dyn Error + Send + Sync + 'static>>,
57    {
58        Self::Fail(err.into())
59    }
60
61    /// Create a restart result.
62    pub fn restart() -> Self {
63        Self::Restart
64    }
65
66    /// Create a completion result.
67    pub fn done() -> Self {
68        Self::Done
69    }
70
71    /// Combine two handler results into one.
72    pub fn combine(self, other: Self) -> Self {
73        match (self, other) {
74            // Acceptance defers to the other.
75            (Self::Accept, next) | (next, Self::Accept) => next,
76            // Completion overrides any other status.
77            (Self::Done, _) | (_, Self::Done) => Self::Done,
78            // Once completion is handled, restart takes precedence.
79            (Self::Restart, _) | (_, Self::Restart) => Self::Restart,
80            // Deferring is next.
81            (Self::Defer(left), Self::Defer(right)) => Self::Defer(format!("{}\n{}", left, right)),
82            (defer @ Self::Defer(_), _) | (_, defer @ Self::Defer(_)) => defer,
83            // Failures are handled next.
84            (fail @ Self::Fail(_), _) | (_, fail @ Self::Fail(_)) => fail,
85            // All we have left are rejections; combine their messages.
86            (Self::Reject(left), Self::Reject(right)) => {
87                Self::Reject(format!("{}\n{}", left, right))
88            },
89        }
90    }
91}
92
93/// An error which can occur when handling a job.
94pub type JobError = Box<dyn Error + Send + Sync>;
95
96/// Interface for handling events.
97pub trait HandlerCore {
98    /// Adds the handler to a director.
99    fn add_to_director<'a>(&'a self, director: &mut Director<'a>) -> Result<(), JobError>;
100}
101
102/// Interface for handling events asynchronously.
103#[async_trait]
104pub trait Handler: HandlerCore {
105    /// The JSON object is passed in and acted upon.
106    async fn handle(
107        &self,
108        kind: &str,
109        object: &Value,
110        retry_count: usize,
111    ) -> Result<JobResult, JobError>;
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::JobResult;
117
118    #[test]
119    fn test_job_result_ctors() {
120        assert!(matches!(JobResult::accept(), JobResult::Accept));
121        if let JobResult::Defer(d) = JobResult::defer("hi") {
122            assert_eq!(d, "hi");
123        } else {
124            panic!("`JobResult::defer` doesn't return a `JobResult::Defer` variant");
125        }
126        if let JobResult::Reject(d) = JobResult::reject("hi") {
127            assert_eq!(d, "hi");
128        } else {
129            panic!("`JobResult::reject` doesn't return a `JobResult::Reject` variant");
130        }
131        if let JobResult::Fail(d) = JobResult::fail("hi") {
132            assert_eq!(format!("{}", d), "hi");
133        } else {
134            panic!("`JobResult::fail` doesn't return a `JobResult::Fail` variant");
135        }
136        assert!(matches!(JobResult::restart(), JobResult::Restart));
137        assert!(matches!(JobResult::done(), JobResult::Done));
138    }
139
140    #[test]
141    fn test_job_result_combine() {
142        let accept = || JobResult::accept();
143        let defer = |d| JobResult::defer(d);
144        let reject = |d| JobResult::reject(d);
145        let fail = |d| JobResult::fail(d);
146        let restart = || JobResult::restart();
147        let done = || JobResult::done();
148
149        assert!(matches!(accept().combine(accept()), JobResult::Accept));
150        if let JobResult::Defer(d) = JobResult::accept().combine(JobResult::defer("defer2")) {
151            assert_eq!(d, "defer2");
152        } else {
153            panic!("`JobResult::Accept` combined with `::Defer` should return a `::Defer`");
154        }
155        if let JobResult::Reject(d) = accept().combine(reject("reject2")) {
156            assert_eq!(d, "reject2");
157        } else {
158            panic!("`JobResult::Accept` combined with `::Reject` should return a `::Reject`");
159        }
160        if let JobResult::Fail(d) = accept().combine(fail("fail2")) {
161            assert_eq!(format!("{}", d), "fail2");
162        } else {
163            panic!("`JobResult::Accept` combined with `::Fail` should return a `::Fail`");
164        }
165        assert!(matches!(accept().combine(restart()), JobResult::Restart));
166        assert!(matches!(accept().combine(done()), JobResult::Done));
167
168        if let JobResult::Defer(d) = defer("defer1").combine(accept()) {
169            assert_eq!(d, "defer1");
170        } else {
171            panic!("`JobResult::Defer` combined with `::Accept` should return a `::Defer`");
172        }
173        if let JobResult::Defer(d) = defer("defer1").combine(defer("defer2")) {
174            assert_eq!(d, "defer1\ndefer2");
175        } else {
176            panic!("`JobResult::Defer` combined with `::Defer` should return a `::Defer`");
177        }
178        if let JobResult::Defer(d) = defer("defer1").combine(reject("reject2")) {
179            assert_eq!(d, "defer1");
180        } else {
181            panic!("`JobResult::Defer` combined with `::Reject` should return a `::Defer`");
182        }
183        if let JobResult::Defer(d) = defer("defer1").combine(fail("fail2")) {
184            assert_eq!(d, "defer1");
185        } else {
186            panic!("`JobResult::Defer` combined with `::Fail` should return a `::Defer`");
187        }
188        assert!(matches!(
189            defer("defer1").combine(restart()),
190            JobResult::Restart,
191        ));
192        assert!(matches!(defer("defer1").combine(done()), JobResult::Done));
193
194        if let JobResult::Reject(d) = reject("reject1").combine(accept()) {
195            assert_eq!(d, "reject1");
196        } else {
197            panic!("`JobResult::Reject` combined with `::Accept` should return a `::Reject`");
198        }
199        if let JobResult::Defer(d) = reject("reject1").combine(defer("defer2")) {
200            assert_eq!(d, "defer2");
201        } else {
202            panic!("`JobResult::Reject` combined with `::Defer` should return a `::Defer`");
203        }
204        if let JobResult::Reject(d) = reject("reject1").combine(reject("reject2")) {
205            assert_eq!(d, "reject1\nreject2");
206        } else {
207            panic!("`JobResult::Reject` combined with `::Reject` should return a `::Reject`");
208        }
209        if let JobResult::Fail(d) = reject("reject1").combine(fail("fail2")) {
210            assert_eq!(format!("{}", d), "fail2");
211        } else {
212            panic!("`JobResult::Reject` combined with `::Fail` should return a `::Fail`");
213        }
214        assert!(matches!(
215            reject("reject1").combine(restart()),
216            JobResult::Restart,
217        ));
218        assert!(matches!(reject("reject1").combine(done()), JobResult::Done));
219
220        if let JobResult::Fail(d) = fail("fail1").combine(accept()) {
221            assert_eq!(format!("{}", d), "fail1");
222        } else {
223            panic!("`JobResult::Fail` combined with `::Accept` should return a `::Fail`");
224        }
225        if let JobResult::Defer(d) = fail("fail1").combine(defer("defer2")) {
226            assert_eq!(format!("{}", d), "defer2");
227        } else {
228            panic!("`JobResult::Fail` combined with `::Defer` should return a `::Defer`");
229        }
230        if let JobResult::Fail(d) = fail("fail1").combine(reject("reject2")) {
231            assert_eq!(format!("{}", d), "fail1");
232        } else {
233            panic!("`JobResult::Fail` combined with `::Reject` should return a `::Fail`");
234        }
235        if let JobResult::Fail(d) = fail("fail1").combine(fail("fail2")) {
236            assert_eq!(format!("{}", d), "fail1");
237        } else {
238            panic!("`JobResult::Fail` combined with `::Fail` should return a `::Fail`");
239        }
240        assert!(matches!(
241            fail("fail1").combine(restart()),
242            JobResult::Restart,
243        ));
244        assert!(matches!(fail("fail1").combine(done()), JobResult::Done));
245
246        assert!(matches!(restart().combine(accept()), JobResult::Restart));
247        assert!(matches!(
248            restart().combine(defer("defer2")),
249            JobResult::Restart,
250        ));
251        assert!(matches!(
252            restart().combine(reject("reject2")),
253            JobResult::Restart,
254        ));
255        assert!(matches!(
256            restart().combine(fail("fail2")),
257            JobResult::Restart,
258        ));
259        assert!(matches!(restart().combine(restart()), JobResult::Restart));
260        assert!(matches!(restart().combine(done()), JobResult::Done));
261
262        assert!(matches!(done().combine(accept()), JobResult::Done));
263        assert!(matches!(done().combine(defer("defer2")), JobResult::Done));
264        assert!(matches!(done().combine(reject("reject2")), JobResult::Done));
265        assert!(matches!(done().combine(fail("fail2")), JobResult::Done));
266        assert!(matches!(done().combine(restart()), JobResult::Done));
267        assert!(matches!(done().combine(done()), JobResult::Done));
268    }
269}