page_hunter/
book.rs

1use std::fmt::{Debug, Display};
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5#[cfg(feature = "utoipa")]
6use utoipa::ToSchema;
7
8#[allow(unused_imports)]
9use crate::{Page, PaginationError};
10
11/// Model to represent a book of paginated items.
12/// #### Fields:
13/// - **sheets**: Represents the ***sheets*** in a [`Book`] as a [`Vec`]  of [`Page`].
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[cfg_attr(feature = "utoipa", derive(ToSchema))]
17pub struct Book<E> {
18    sheets: Vec<Page<E>>,
19}
20
21impl<E> Book<E> {
22    /// Get ***sheets***
23    pub fn get_sheets(&self) -> &Vec<Page<E>> {
24        &self.sheets
25    }
26
27    /// Create a new [`Book`] instance.
28    ///
29    /// ### Arguments:
30    /// - **sheets**: A reference to a [`Vec`] of  [`Page`], where `E` must implement [`Clone`].
31    ///
32    /// ### Returns:
33    /// A [`Book`] if successful, otherwise a [`PaginationError`] is returned.
34    ///
35    /// ### Example:
36    /// ```rust,no_run
37    ///   use page_hunter::*;
38    ///
39    ///   let sheets: Vec<Page<u32>> = vec![
40    ///     Page::new(&vec![1, 2], 0, 2, 5).unwrap_or_else(|error| {
41    ///       panic!("Error creating page model: {:?}", error);
42    ///     }),
43    ///     Page::new(&vec![3, 4], 1, 2, 5).unwrap_or_else(|error| {
44    ///       panic!("Error creating page model: {:?}", error);
45    ///     }),
46    ///   ];
47    ///
48    ///   let book: Book<u32> = Book::new(&sheets);
49    /// ```
50    pub fn new(sheets: &Vec<Page<E>>) -> Book<E>
51    where
52        E: Clone,
53    {
54        Book {
55            sheets: sheets.to_owned(),
56        }
57    }
58}
59
60/// Implementation of [`Default`] for [`Book`].
61impl<E> Default for Book<E> {
62    fn default() -> Self {
63        Self { sheets: Vec::new() }
64    }
65}
66
67/// Implementation of [`Display`] for [`Book`].
68impl<E> Display for Book<E>
69where
70    E: Debug,
71{
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(f, "Book {{ sheets: {:?} }}", self.sheets)
74    }
75}
76
77/// Implementation of [`IntoIterator`] for [`Book`].
78impl<E> IntoIterator for Book<E> {
79    type Item = Page<E>;
80    type IntoIter = std::vec::IntoIter<Self::Item>;
81
82    fn into_iter(self) -> Self::IntoIter {
83        self.sheets.into_iter()
84    }
85}
86
87#[cfg(test)]
88mod test_book {
89    use crate::*;
90
91    /// Test [`Book] constructor.
92    #[test]
93    fn test_book_constructor() {
94        let records: Vec<u32> = vec![1, 2, 3, 4, 5];
95        let size: usize = 2;
96
97        let page_1: Page<u32> = Page::new(&records[0..2].to_vec(), 0, size, records.len()).unwrap();
98        let page_2: Page<u32> = Page::new(&records[2..4].to_vec(), 1, size, records.len()).unwrap();
99        let page_3: Page<u32> = Page::new(&records[4..5].to_vec(), 2, size, records.len()).unwrap();
100
101        Book::new(&vec![page_1, page_2, page_3]);
102    }
103
104    /// Test [`Book] clone method.
105    #[test]
106    fn test_book_clone() {
107        let records: Vec<u32> = vec![1, 2, 3, 4, 5];
108        let size: usize = 2;
109
110        let page_1: Page<u32> = Page::new(&records[0..2].to_vec(), 0, size, records.len()).unwrap();
111        let page_2: Page<u32> = Page::new(&records[2..4].to_vec(), 1, size, records.len()).unwrap();
112        let page_3: Page<u32> = Page::new(&records[4..5].to_vec(), 2, size, records.len()).unwrap();
113
114        let book: Book<u32> = Book::new(&vec![page_1, page_2, page_3]);
115        let cloned_book: Book<u32> = book.clone();
116
117        assert_eq!(
118            book.get_sheets()[0].get_items(),
119            cloned_book.get_sheets()[0].get_items()
120        );
121        assert_eq!(
122            book.get_sheets()[1].get_items(),
123            cloned_book.get_sheets()[1].get_items()
124        );
125        assert_eq!(
126            book.get_sheets()[2].get_items(),
127            cloned_book.get_sheets()[2].get_items()
128        );
129
130        assert_eq!(
131            book.get_sheets()[0].get_page(),
132            cloned_book.get_sheets()[0].get_page()
133        );
134        assert_eq!(
135            book.get_sheets()[1].get_page(),
136            cloned_book.get_sheets()[1].get_page()
137        );
138        assert_eq!(
139            book.get_sheets()[2].get_page(),
140            cloned_book.get_sheets()[2].get_page()
141        );
142
143        assert_eq!(
144            book.get_sheets()[0].get_size(),
145            cloned_book.get_sheets()[0].get_size()
146        );
147        assert_eq!(
148            book.get_sheets()[1].get_size(),
149            cloned_book.get_sheets()[1].get_size()
150        );
151        assert_eq!(
152            book.get_sheets()[2].get_size(),
153            cloned_book.get_sheets()[2].get_size()
154        );
155
156        assert_eq!(
157            book.get_sheets()[0].get_total(),
158            cloned_book.get_sheets()[0].get_total()
159        );
160        assert_eq!(
161            book.get_sheets()[1].get_total(),
162            cloned_book.get_sheets()[1].get_total()
163        );
164        assert_eq!(
165            book.get_sheets()[2].get_total(),
166            cloned_book.get_sheets()[2].get_total()
167        );
168
169        assert_eq!(
170            book.get_sheets()[0].get_pages(),
171            cloned_book.get_sheets()[0].get_pages()
172        );
173        assert_eq!(
174            book.get_sheets()[1].get_pages(),
175            cloned_book.get_sheets()[1].get_pages()
176        );
177        assert_eq!(
178            book.get_sheets()[2].get_pages(),
179            cloned_book.get_sheets()[2].get_pages()
180        );
181
182        assert_eq!(
183            book.get_sheets()[0].get_previous_page(),
184            cloned_book.get_sheets()[0].get_previous_page()
185        );
186        assert_eq!(
187            book.get_sheets()[1].get_previous_page(),
188            cloned_book.get_sheets()[1].get_previous_page()
189        );
190        assert_eq!(
191            book.get_sheets()[2].get_previous_page(),
192            cloned_book.get_sheets()[2].get_previous_page()
193        );
194
195        assert_eq!(
196            book.get_sheets()[0].get_next_page(),
197            cloned_book.get_sheets()[0].get_next_page()
198        );
199        assert_eq!(
200            book.get_sheets()[1].get_next_page(),
201            cloned_book.get_sheets()[1].get_next_page()
202        );
203        assert_eq!(
204            book.get_sheets()[2].get_next_page(),
205            cloned_book.get_sheets()[2].get_next_page()
206        );
207    }
208
209    /// Test [`Book] display method.
210    #[test]
211    fn test_book_display() {
212        let records: Vec<u32> = vec![1, 2, 3, 4, 5];
213        let size: usize = 2;
214
215        let page_1: Page<u32> = Page::new(&records[0..2].to_vec(), 0, size, records.len()).unwrap();
216        let page_2: Page<u32> = Page::new(&records[2..4].to_vec(), 1, size, records.len()).unwrap();
217        let page_3: Page<u32> = Page::new(&records[4..5].to_vec(), 2, size, records.len()).unwrap();
218
219        let book: Book<u32> = Book::new(&vec![page_1, page_2, page_3]);
220
221        assert_eq!(
222            format!("{}", book),
223            "Book { sheets: [Page { items: [1, 2], page: 0, size: 2, total: 5, pages: 3, previous_page: None, next_page: Some(1) }, Page { items: [3, 4], page: 1, size: 2, total: 5, pages: 3, previous_page: Some(0), next_page: Some(2) }, Page { items: [5], page: 2, size: 2, total: 5, pages: 3, previous_page: Some(1), next_page: None }] }"
224        );
225    }
226
227    /// Test [`Book] into_iter method.
228    #[test]
229    fn test_book_into_iter() {
230        let records: Vec<u32> = vec![1, 2, 3, 4, 5];
231        let size: usize = 2;
232
233        let page_1: Page<u32> = Page::new(&records[0..2].to_vec(), 0, size, records.len()).unwrap();
234        let page_2: Page<u32> = Page::new(&records[2..4].to_vec(), 1, size, records.len()).unwrap();
235        let page_3: Page<u32> = Page::new(&records[4..5].to_vec(), 2, size, records.len()).unwrap();
236
237        let book: Book<u32> = Book::new(&vec![page_1, page_2, page_3]);
238
239        let mut iter = book.into_iter();
240
241        assert!(iter.next().is_some());
242        assert!(iter.next().is_some());
243        assert!(iter.next().is_some());
244        assert!(iter.next().is_none());
245    }
246
247    /// Test [`Book`] debug method.
248    #[test]
249    fn test_book_debug() {
250        let records: Vec<u32> = vec![1, 2, 3, 4, 5];
251        let size: usize = 2;
252
253        let page_1: Page<u32> = Page::new(&records[0..2].to_vec(), 0, size, records.len()).unwrap();
254        let page_2: Page<u32> = Page::new(&records[2..4].to_vec(), 1, size, records.len()).unwrap();
255
256        let book: Book<u32> = Book::new(&vec![page_1, page_2]);
257
258        assert_eq!(
259            format!("{:?}", book),
260            "Book { sheets: [Page { items: [1, 2], page: 0, size: 2, total: 5, pages: 3, previous_page: None, next_page: Some(1) }, Page { items: [3, 4], page: 1, size: 2, total: 5, pages: 3, previous_page: Some(0), next_page: Some(2) }] }"
261        );
262    }
263
264    /// Test [`Book`] default method.
265    #[test]
266    fn test_book_default() {
267        let book: Book<u32> = Book::default();
268        assert_eq!(book.get_sheets().len(), 0);
269    }
270
271    /// Test [`Book] serialization and deserialization methods.
272    #[cfg(feature = "serde")]
273    #[test]
274    fn test_book_serialization_and_deserialization() {
275        let records: Vec<u32> = vec![1, 2, 3, 4, 5];
276        let size: usize = 2;
277
278        let page_1: Page<u32> = Page::new(&records[0..2].to_vec(), 0, size, records.len()).unwrap();
279        let page_2: Page<u32> = Page::new(&records[2..4].to_vec(), 1, size, records.len()).unwrap();
280        let page_3: Page<u32> = Page::new(&records[4..5].to_vec(), 2, size, records.len()).unwrap();
281
282        let book: Book<u32> = Book::new(&vec![page_1, page_2, page_3]);
283
284        let serialized_book: String = serde_json::to_string(&book).unwrap();
285        let _deserialized_book: Book<u32> = serde_json::from_str(&serialized_book).unwrap();
286    }
287
288    /// Test [`Book] deserialization error.
289    #[cfg(feature = "serde")]
290    #[test]
291    fn test_book_deserialization_error() {
292        let serialized_book: String = r#"{"sheets":[{"items":[1,2],"page":0,"size":2,"total":5,"pages":3,"previous_page":null,"next_page":1},{"items":[3,4],"page":1,"size":2,"total":5,"pages":3,"previous_page":0,"next_page":2},{"items":[5],"page":2,"size":2,"total":5,"pages":3,"previous_page":1,"next_page":3}]}"#.to_string();
293
294        let book_result: Result<Book<u32>, serde_json::Error> =
295            serde_json::from_str(&serialized_book);
296        assert!(book_result.is_err());
297        assert_eq!(
298            format!("{}", book_result.unwrap_err()),
299            "INVALID VALUE ERROR- Next page index error: expected 'None', found 'Some(3)' at line 1 column 270"
300        );
301    }
302
303    /// Test [`Book] to schema.
304    #[cfg(feature = "utoipa")]
305    #[test]
306    fn test_book_to_schema() {
307        use utoipa::{
308            PartialSchema, ToSchema,
309            openapi::{RefOr, Schema},
310        };
311
312        #[derive(Clone, ToSchema)]
313        #[allow(dead_code)]
314        struct Record {
315            number: u8,
316        }
317
318        let schema_object: RefOr<Schema> = Book::<Record>::schema();
319
320        let json_string: String = serde_json::to_string(&schema_object).unwrap();
321        assert_eq!(
322            json_string,
323            "{\"type\":\"object\",\"description\":\"Model to represent a book of paginated items.\\n#### Fields:\\n- **sheets**: Represents the ***sheets*** in a [`Book`] as a [`Vec`]  of [`Page`].\",\"required\":[\"sheets\"],\"properties\":{\"sheets\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Page_Record\"}}}}"
324        );
325    }
326}