Skip to main content

spring_batch_rs/item/fake/
person_reader.rs

1use std::cell::Cell;
2use std::fmt;
3
4use ::serde::{Deserialize, Serialize, Serializer, ser::Error};
5use fake::faker::internet::raw::*;
6use fake::locales::*;
7use fake::{Fake, faker::name::raw::*};
8use log::debug;
9use rand::RngExt;
10
11use time::format_description;
12use time::{Date, Month};
13
14use crate::core::item::ItemReader;
15use crate::core::item::ItemReaderResult;
16
17/// Represents a person with their personal information.
18#[derive(Serialize, Deserialize, Clone)]
19pub struct Person {
20    first_name: String,
21    last_name: String,
22    title: String,
23    email: String,
24    #[serde(serialize_with = "date_serializer")]
25    birth_date: Date,
26}
27
28/// Serializes a `Date` object into a string representation.
29fn date_serializer<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
30where
31    S: Serializer,
32{
33    let format = format_description::parse_borrowed::<2>("[year]-[month]-[day]")
34        .map_err(|e| Error::custom(e.to_string()))?;
35    let s = date
36        .format(&format)
37        .map_err(|e| Error::custom(e.to_string()))?;
38    serializer.serialize_str(&s)
39}
40
41impl fmt::Display for Person {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(
44            f,
45            "first_name:{}, last_name:{}, birth_date:{}",
46            self.first_name, self.last_name, self.birth_date
47        )
48    }
49}
50
51/// A reader for generating fake `Person` objects.
52///
53/// # Examples
54///
55/// ```
56/// use spring_batch_rs::item::fake::person_reader::PersonReaderBuilder;
57/// use spring_batch_rs::core::item::ItemReader;
58///
59/// let reader = PersonReaderBuilder::new().number_of_items(3).build();
60///
61/// let person1 = reader.read().unwrap();
62/// assert!(person1.is_some());
63///
64/// let person2 = reader.read().unwrap();
65/// assert!(person2.is_some());
66///
67/// let person3 = reader.read().unwrap();
68/// assert!(person3.is_some());
69///
70/// let done = reader.read().unwrap();
71/// assert!(done.is_none());
72/// ```
73pub struct PersonReader {
74    count: Cell<usize>,
75}
76
77impl ItemReader<Person> for PersonReader {
78    /// Reads the next `Person` object.
79    ///
80    /// Returns `Ok(Some(person))` if there are more `Person` objects to read.
81    /// Returns `Ok(None)` if there are no more `Person` objects to read.
82    fn read(&self) -> ItemReaderResult<Person> {
83        if self.count.get() == 0 {
84            return Ok(None);
85        }
86
87        self.count.set(self.count.get() - 1);
88
89        let person = Person {
90            first_name: FirstName(FR_FR).fake(),
91            last_name: LastName(FR_FR).fake(),
92            title: Title(FR_FR).fake(),
93            email: FreeEmail(FR_FR).fake(),
94            birth_date: fake_date(),
95        };
96        debug!("Person: {}", person);
97        Ok(Some(person))
98    }
99}
100
101/// Generates a random `Date` object.
102fn fake_date() -> Date {
103    let mut rng = rand::rng();
104    let year = rng.random_range(1900..2022);
105    let month = rng.random_range(1..=12);
106    let day = rng.random_range(1..=28);
107
108    Date::from_calendar_date(year, Month::try_from(month).unwrap(), day).unwrap()
109}
110
111/// Builder for creating a `PersonReader`.
112#[derive(Default)]
113pub struct PersonReaderBuilder {
114    number_of_items: usize,
115}
116
117impl PersonReaderBuilder {
118    /// Creates a new `PersonReaderBuilder` instance.
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// use spring_batch_rs::item::fake::person_reader::PersonReaderBuilder;
124    ///
125    /// let builder = PersonReaderBuilder::new();
126    /// assert!(true); // builder is created successfully
127    /// ```
128    pub fn new() -> Self {
129        Self { number_of_items: 0 }
130    }
131
132    /// Sets the number of `Person` objects to generate.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use spring_batch_rs::item::fake::person_reader::PersonReaderBuilder;
138    ///
139    /// let builder = PersonReaderBuilder::new().number_of_items(10);
140    /// assert!(true); // number_of_items is set successfully
141    /// ```
142    pub fn number_of_items(mut self, number_of_items: usize) -> Self {
143        self.number_of_items = number_of_items;
144        self
145    }
146
147    /// Builds a `PersonReader` instance with the configured settings.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use spring_batch_rs::item::fake::person_reader::PersonReaderBuilder;
153    /// use spring_batch_rs::core::item::ItemReader;
154    ///
155    /// let reader = PersonReaderBuilder::new().number_of_items(2).build();
156    ///
157    /// let first = reader.read().unwrap();
158    /// assert!(first.is_some());
159    ///
160    /// let second = reader.read().unwrap();
161    /// assert!(second.is_some());
162    ///
163    /// let none = reader.read().unwrap();
164    /// assert!(none.is_none());
165    /// ```
166    pub fn build(self) -> PersonReader {
167        PersonReader {
168            count: self.number_of_items.into(),
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::{PersonReader, PersonReaderBuilder};
176    use crate::core::item::ItemReader;
177
178    #[test]
179    fn should_read_configured_number_of_persons() {
180        let reader: PersonReader = PersonReaderBuilder::new().number_of_items(2).build();
181        assert_eq!(reader.count.get(), 2);
182
183        let result1 = reader.read();
184        assert_eq!(reader.count.get(), 1);
185        assert!(result1.is_ok());
186
187        let person = result1.unwrap();
188        assert!(person.is_some());
189        assert!(!person.as_ref().unwrap().first_name.is_empty());
190        assert!(!person.as_ref().unwrap().last_name.is_empty());
191
192        let result2 = reader.read();
193        assert_eq!(reader.count.get(), 0);
194        assert!(result2.is_ok());
195        assert!(result2.unwrap().is_some());
196
197        let result3 = reader.read();
198        assert_eq!(reader.count.get(), 0);
199        assert!(result3.unwrap().is_none());
200    }
201
202    #[test]
203    fn should_display_person_with_all_fields() {
204        let reader = PersonReaderBuilder::new().number_of_items(1).build();
205        let person = reader.read().unwrap().unwrap();
206        let text = format!("{}", person);
207        assert!(
208            text.contains("first_name:"),
209            "Display missing first_name: {text}"
210        );
211        assert!(
212            text.contains("last_name:"),
213            "Display missing last_name: {text}"
214        );
215        assert!(
216            text.contains("birth_date:"),
217            "Display missing birth_date: {text}"
218        );
219    }
220
221    #[test]
222    fn should_serialize_person_with_date_in_iso_format() {
223        let reader = PersonReaderBuilder::new().number_of_items(1).build();
224        let person = reader.read().unwrap().unwrap();
225        let json = serde_json::to_string(&person).expect("serialization must succeed");
226        assert!(
227            json.contains("birth_date"),
228            "missing birth_date key: {json}"
229        );
230        // Date must follow YYYY-MM-DD pattern (at least two hyphens)
231        let hyphen_count = json.chars().filter(|&c| c == '-').count();
232        assert!(hyphen_count >= 2, "date should contain hyphens: {json}");
233    }
234}