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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// rtj provides a generic job execution framework in Rust
// Copyright 2021-2022 Anthony Martinez
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

/// The core trait [`Job`] providing required methods to user-defined data types for
/// orchestrating task execution.
///
/// It is left to the implementor to ensure these methods do not panic or that errors
/// are handled as they see fit.
pub trait Job {
    fn encode(&self) -> Vec<u8>;
    fn decode(input: &[u8]) -> Self;
    fn ack(&self) -> Vec<u8>;
    fn run(&self) -> std::result::Result<(), Box<dyn std::error::Error>>;
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::*;

    // Testing with Rust MessagePack implementation for Serialization/Deserialization
    use rmp_serde as rmps;

    enum MsgType {
        Hello,
        Unknown,
    }

    impl From<u8> for MsgType {
        fn from(t: u8) -> MsgType {
            match t {
                0 => MsgType::Hello,
                _ => MsgType::Unknown,
            }
        }
    }

    impl From<MsgType> for u8 {
        fn from(t: MsgType) -> u8 {
            match t {
                MsgType::Hello => 0,
                MsgType::Unknown => 255,
            }
        }
    }

    #[derive(Default, Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
    pub struct Hello {
        name: String,
        age: u8,
    }

    impl Job for Hello {
        fn encode(&self) -> Vec<u8> {
            rmps::to_vec(&self).unwrap()
        }

        fn decode(input: &[u8]) -> Hello {
            let hello: Hello = rmps::from_read(input).unwrap();
            hello
        }

        fn ack(&self) -> Vec<u8> {
	    let name = &self.name;
	    let age = &self.age;
            let ack_string = format!("Hello from {name}, aged {age}");
            Vec::from(ack_string)
        }

        fn run(&self) -> std::result::Result<(), Box<dyn std::error::Error>> {
            self.ack();
            Ok(())
        }
    }

    #[test]
    fn test_job_encode_decode() {
        let hello = Hello {
            name: "Anthony J. Martinez".to_owned(),
            age: 38,
        };

        let encoded = hello.encode();
        let decoded = Hello::decode(&encoded);

        assert_eq!(hello, decoded)
    }

    #[test]
    fn test_job_ack() {
        let hello = Hello {
            name: "Anthony J. Martinez".to_owned(),
            age: 38,
        };

        let hello_vec = hello.ack();

        assert_eq!(
            hello_vec,
            Vec::from("Hello from Anthony J. Martinez, aged 38")
        )
    }

    #[test]
    fn test_job_run() {
        let hello = Hello {
            name: "Anthony J. Martinez".to_owned(),
            age: 38,
        };

        assert!(hello.run().is_ok())
    }
}