Skip to main content

spring_batch_rs/item/fake/
person_reader.rs

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