use serde::{Deserialize, Serialize};
use spring_batch_rs::{
BatchError,
core::{
item::{ItemProcessor, PassThroughProcessor},
job::{Job, JobBuilder},
step::StepBuilder,
},
item::{
csv::csv_reader::CsvItemReaderBuilder, csv::csv_writer::CsvItemWriterBuilder,
json::json_writer::JsonItemWriterBuilder,
},
};
use std::env::temp_dir;
#[derive(Debug, Clone, Deserialize, Serialize)]
struct Product {
id: u32,
name: String,
price: f64,
category: String,
}
struct DiscountProcessor {
discount_percent: f64,
}
impl DiscountProcessor {
fn new(discount_percent: f64) -> Self {
Self { discount_percent }
}
}
impl ItemProcessor<Product, Product> for DiscountProcessor {
fn process(&self, item: &Product) -> Result<Option<Product>, BatchError> {
Ok(Some(Product {
id: item.id,
name: item.name.clone(),
price: item.price * (1.0 - self.discount_percent / 100.0),
category: item.category.clone(),
}))
}
}
fn example_csv_to_csv() -> Result<(), BatchError> {
println!("=== Example 1: Basic CSV to CSV ===");
let csv_data = "\
id,name,price,category
1,Laptop,999.99,Electronics
2,Coffee Mug,12.99,Kitchen
3,Notebook,5.99,Office
4,Headphones,79.99,Electronics";
let reader = CsvItemReaderBuilder::<Product>::new()
.has_headers(true)
.from_reader(csv_data.as_bytes());
let output_path = temp_dir().join("products_copy.csv");
let writer = CsvItemWriterBuilder::<Product>::new()
.has_headers(true)
.from_path(&output_path);
let processor = PassThroughProcessor::<Product>::new();
let step = StepBuilder::new("csv-to-csv")
.chunk::<Product, Product>(2)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
let result = job.run()?;
println!(" Output: {}", output_path.display());
println!(" Duration: {:?}", result.duration);
Ok(())
}
fn example_csv_to_json_with_processor() -> Result<(), BatchError> {
println!("\n=== Example 2: CSV to JSON with Processor ===");
let csv_data = "\
id,name,price,category
1,Laptop,999.99,Electronics
2,Coffee Mug,12.99,Kitchen
3,Notebook,5.99,Office";
let reader = CsvItemReaderBuilder::<Product>::new()
.has_headers(true)
.from_reader(csv_data.as_bytes());
let output_path = temp_dir().join("products_discounted.json");
let writer = JsonItemWriterBuilder::<Product>::new()
.pretty_formatter(true)
.from_path(&output_path);
let processor = DiscountProcessor::new(10.0);
let step = StepBuilder::new("csv-to-json-discount")
.chunk::<Product, Product>(10)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
let result = job.run()?;
println!(" Applied 10% discount to all products");
println!(" Output: {}", output_path.display());
println!(" Duration: {:?}", result.duration);
Ok(())
}
fn example_custom_delimiter() -> Result<(), BatchError> {
println!("\n=== Example 3: Custom Delimiter (semicolon) ===");
let csv_data = "\
id;name;price;category
1;Laptop;999.99;Electronics
2;Coffee Mug;12.99;Kitchen";
let reader = CsvItemReaderBuilder::<Product>::new()
.has_headers(true)
.delimiter(b';')
.from_reader(csv_data.as_bytes());
let output_path = temp_dir().join("products_semicolon.csv");
let writer = CsvItemWriterBuilder::<Product>::new()
.has_headers(true)
.delimiter(b';')
.from_path(&output_path);
let processor = PassThroughProcessor::<Product>::new();
let step = StepBuilder::new("semicolon-csv")
.chunk::<Product, Product>(10)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
println!(" Processed semicolon-delimited CSV");
println!(" Output: {}", output_path.display());
Ok(())
}
fn example_fault_tolerance() -> Result<(), BatchError> {
println!("\n=== Example 4: Fault Tolerance ===");
let csv_data = "\
id,name,price,category
1,Laptop,999.99,Electronics
2,Coffee Mug,12.99,Kitchen
3,Invalid Item,bad_price,Error
4,Notebook,5.99,Office";
let reader = CsvItemReaderBuilder::<Product>::new()
.has_headers(true)
.from_reader(csv_data.as_bytes());
let output_path = temp_dir().join("products_valid.json");
let writer = JsonItemWriterBuilder::<Product>::new().from_path(&output_path);
let processor = PassThroughProcessor::<Product>::new();
let step = StepBuilder::new("fault-tolerant-csv")
.chunk::<Product, Product>(2)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.skip_limit(1) .build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
let step_exec = job.get_step_execution("fault-tolerant-csv").unwrap();
println!(" Read count: {}", step_exec.read_count);
println!(" Write count: {}", step_exec.write_count);
println!(" Read errors (skipped): {}", step_exec.read_error_count);
println!(" Output: {}", output_path.display());
Ok(())
}
fn main() -> Result<(), BatchError> {
println!("CSV Processing Examples");
println!("=======================\n");
example_csv_to_csv()?;
example_csv_to_json_with_processor()?;
example_custom_delimiter()?;
example_fault_tolerance()?;
println!("\n✓ All CSV examples completed successfully!");
Ok(())
}