spring_batch_rs/core/
job.rs

1use std::time::{Duration, Instant};
2
3use log::info;
4use uuid::Uuid;
5
6use crate::BatchError;
7
8use super::{build_name, step::Step};
9
10type JobResult<T> = Result<T, BatchError>;
11
12/// Represents a job that can be executed.
13pub trait Job {
14    /// Runs the job and returns the result of the job execution.
15    fn run(&self) -> JobResult<JobExecution>;
16}
17
18/// Represents the execution of a job.
19#[derive(Debug)]
20pub struct JobExecution {
21    pub start: Instant,
22    pub end: Instant,
23    pub duration: Duration,
24}
25
26/// Represents an instance of a job.
27pub struct JobInstance<'a> {
28    id: Uuid,
29    name: String,
30    steps: Vec<&'a dyn Step>,
31}
32
33impl<'a> Job for JobInstance<'a> {
34    fn run(&self) -> JobResult<JobExecution> {
35        let start = Instant::now();
36
37        info!("Start of job: {}, id: {}", self.name, self.id);
38        let steps = &self.steps;
39        for step in steps {
40            let result = step.execute();
41
42            if result.is_err() {
43                return Err(BatchError::Step(step.get_name().to_owned()));
44            }
45        }
46        info!("End of job: {}, id: {}", self.name, self.id);
47
48        let job_execution = JobExecution {
49            start,
50            end: Instant::now(),
51            duration: start.elapsed(),
52        };
53
54        Ok(job_execution)
55    }
56}
57
58/// Builder for creating a job instance.
59#[derive(Default)]
60pub struct JobBuilder<'a> {
61    name: Option<String>,
62    steps: Vec<&'a dyn Step>,
63}
64
65impl<'a> JobBuilder<'a> {
66    /// Creates a new `JobBuilder` instance.
67    pub fn new() -> Self {
68        Self {
69            name: None,
70            steps: Vec::new(),
71        }
72    }
73
74    /// Sets the name of the job.
75    pub fn name(mut self, name: String) -> JobBuilder<'a> {
76        self.name = Some(name);
77        self
78    }
79
80    /// Sets the first step of the job.
81    pub fn start(mut self, step: &'a dyn Step) -> JobBuilder<'a> {
82        self.steps.push(step);
83        self
84    }
85
86    /// Adds a step to the job.
87    pub fn next(mut self, step: &'a dyn Step) -> JobBuilder<'a> {
88        self.steps.push(step);
89        self
90    }
91
92    /// Builds and returns a `JobInstance` based on the configured parameters.
93    pub fn build(self) -> JobInstance<'a> {
94        JobInstance {
95            id: Uuid::new_v4(),
96            name: self.name.unwrap_or(build_name()),
97            steps: self.steps,
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use anyhow::Result;
105
106    use std::{
107        env::{self, temp_dir},
108        fs::File,
109        path::Path,
110    };
111
112    use serde::{Deserialize, Serialize};
113
114    use crate::{
115        core::step::{StepBuilder, StepInstance},
116        item::csv::csv_writer::CsvItemWriterBuilder,
117        item::json::json_reader::JsonItemReaderBuilder,
118    };
119
120    use super::{Job, JobBuilder};
121
122    #[derive(Serialize, Deserialize, Clone)]
123    pub struct Person {
124        first_name: String,
125        last_name: String,
126        title: String,
127        email: String,
128    }
129
130    #[test]
131    fn this_test_will_pass() -> Result<()> {
132        env::set_var("RUST_LOG", "INFO");
133        env_logger::init();
134
135        let path = Path::new("examples/data/persons.json");
136
137        let file = File::open(path).expect("Unable to open file");
138
139        let reader = JsonItemReaderBuilder::new().from_reader(file);
140
141        let writer = CsvItemWriterBuilder::new()
142            .has_headers(true)
143            .from_path(temp_dir().join("persons.csv"));
144
145        let step: StepInstance<Person, Person> = StepBuilder::new()
146            .reader(&reader)
147            .writer(&writer)
148            .chunk(2)
149            .build();
150
151        let job = JobBuilder::new()
152            .name("test".to_string())
153            .start(&step)
154            .build();
155        let _result = job.run();
156
157        Ok(())
158    }
159}