spring_batch_rs/core/
job.rs1use 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
12pub trait Job {
14 fn run(&self) -> JobResult<JobExecution>;
16}
17
18#[derive(Debug)]
20pub struct JobExecution {
21 pub start: Instant,
22 pub end: Instant,
23 pub duration: Duration,
24}
25
26pub 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#[derive(Default)]
60pub struct JobBuilder<'a> {
61 name: Option<String>,
62 steps: Vec<&'a dyn Step>,
63}
64
65impl<'a> JobBuilder<'a> {
66 pub fn new() -> Self {
68 Self {
69 name: None,
70 steps: Vec::new(),
71 }
72 }
73
74 pub fn name(mut self, name: String) -> JobBuilder<'a> {
76 self.name = Some(name);
77 self
78 }
79
80 pub fn start(mut self, step: &'a dyn Step) -> JobBuilder<'a> {
82 self.steps.push(step);
83 self
84 }
85
86 pub fn next(mut self, step: &'a dyn Step) -> JobBuilder<'a> {
88 self.steps.push(step);
89 self
90 }
91
92 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}