page_hunter/
lib.rs

1//! ***Page Hunter*** library is a Rust-based pagination tool that provides a way to manage and navigate through pages of data.
2//! It offers a set of resources that encapsulates all the necessary pagination information such as the current page, total pages, previous page, next page and the items on the current page.
3//!
4//! The library also includes validation methods to ensure the integrity of the pagination data.
5//! It's designed to be flexible and easy to integrate into any Rust project that requires pagination functionality standard data validation
6//!
7//! ## CRATE FEATURES
8//! - `serde`: Add [Serialize](https://docs.rs/serde/1.0.200/serde/trait.Serialize.html) and [Deserialize](https://docs.rs/serde/1.0.200/serde/trait.Deserialize.html) support for [`Page`] and [`Book`] based on [serde](https://crates.io/crates/serde/1.0.200). This feature is useful for implementing pagination models as a request or response body in REST APIs, among other implementations.
9//!  - `utoipa`: Add [ToSchema](https://docs.rs/utoipa/4.2.0/utoipa/trait.ToSchema.html) support for [`Page`] and  [`Book`] based on [utoipa](https://crates.io/crates/utoipa/4.2.0). This feature is useful for generating OpenAPI schemas for pagination models. This feature depends on the `serde` feature and therefore you only need to implement `utoipa` to get both.
10//! - `sqlx`: Add support for pagination with [SQLx](https://docs.rs/sqlx/0.8.1/sqlx/) for Postgres, MySQL and SQLite databases.
11//!
12//! ## BASIC OPERATION
13//!
14//! The **page-hunter** library provides two main models to manage pagination:
15//! - [`Page`]: Represents a page of records with the current page, total pages, previous page, next page, and the items on the current page.
16//! - [`Book`]: Represents a book of pages with a collection of [`Page`] instances.
17//!
18//! The library also provides a set of functions to paginate records into a [`Page`] model and bind records into a [`Book`] model. The following examples show how to use the **page-hunter** library:
19//!
20//! #### Paginate records:
21//! If you need to paginate records and get a specific [`Page`]:
22//! ```rust,no_run
23//!   use page_hunter::{Page, paginate_records, RecordsPagination};
24//!
25//!   let records: Vec<u32> = vec![1, 2, 3, 4, 5];
26//!   let page: usize = 0;
27//!   let size: usize = 2;
28//!
29//!   // Using the paginate_records function:
30//!   let page_model: Page<u32> = match paginate_records(&records, page, size) {
31//!     Ok(p) => p,
32//!     Err(e) => panic!("Error paginating records: {:?}", e),
33//!   };
34//!
35//!   // Using RecordsPagination trait:
36//!   let page_model: Page<u32> = match records.paginate(page, size) {
37//!     Ok(p) => p,
38//!     Err(e) => panic!("Error paginating records: {:?}", e),
39//!   };
40//! ```
41//!
42//! To create a new `Page` instance from known parameters:
43//! ```rust,no_run
44//!   use page_hunter::{Page, PaginationResult};
45//!
46//!   let items: Vec<u32> = vec![1, 2];
47//!   let page: usize = 0;
48//!   let size: usize = 2;
49//!   let total_elements: usize = 5;
50//!
51//!   let page_model_result: PaginationResult<Page<u32>> = Page::new(
52//!     &items,
53//!     page,
54//!     size,
55//!     total_elements,
56//!   );
57//! ```
58//!
59//! On feature `serde` enabled, you can serialize and deserialize a [`Page`] as follows:
60//! ```rust,no_run
61//!   use page_hunter::Page;
62//!
63//!   let items: Vec<u32> = vec![1, 2];
64//!   let page: usize = 0;
65//!   let size: usize = 2;
66//!   let total_elements: usize = 5;
67//!
68//!   let page_model: Page<u32> = Page::new(
69//!     &items,
70//!     page,
71//!     size,
72//!     total_elements,
73//!   ).unwrap_or_else(|error| {
74//!     panic!("Error creating page model: {:?}", error);
75//!   });
76//!
77//!   let serialized_page: String = serde_json::to_string(&page_model)
78//!     .unwrap_or_else(|error| {
79//!       panic!("Error serializing page model: {:?}", error);
80//!   });
81//!
82//!   let deserialized_page: Page<u32> = serde_json::from_str(&serialized_page)
83//!     .unwrap_or_else(|error| {
84//!       panic!("Error deserializing page model: {:?}", error);
85//!   });
86//! ```
87//!
88//! When you create a new [`Page`] instance from the constructor or deserialization, the following rules are validated for the fields on the page:
89//! - ***pages*** must be equal to ***total*** divided by ***size*** rounded up. When ***size*** is 0, ***pages*** must be 1.
90//! - ***page*** must be less than or equal to ***pages*** - 1.
91//! - if ***page*** is less than ***pages*** - 1, ***items*** length must be equal to ***size***.
92//! - if ***page*** is equal to ***pages*** - 1, ***total*** must be equal to (***pages*** - 1) * ***size*** + ***items*** length.
93//! - ***previous_page*** must be equal to ***page*** - 1 if ***page*** is greater than 0, otherwise it must be [`None`].
94//! - ***next_page*** must be equal to ***page*** + 1 if ***page*** is less than ***pages*** - 1, otherwise it must be [`None`].
95//!
96//! If any of these rules are violated, a [`PaginationError`] will be returned.
97//!
98//! #### Bind records:
99//! If you need to bind records into a [`Book`] model:
100//! ```rust,no_run
101//!   use page_hunter::{bind_records, Book, RecordsPagination};
102//!
103//!   let records: Vec<u32> = vec![1, 2, 3, 4, 5];
104//!   let size: usize = 2;
105//!
106//!   // Using the bind_records function:
107//!   let book: Book<u32> = match bind_records(&records, size) {
108//!     Ok(b) => b,
109//!     Err(e) => panic!("Error binding records: {:?}", e),
110//!   };
111//!
112//!   // Using RecordsPagination trait:
113//!   let book: Book<u32> = match records.bind(size) {
114//!     Ok(b) => b,
115//!     Err(e) => panic!("Error binding records: {:?}", e),
116//!   };
117//! ```
118//!
119//! To create a new [`Book`] instance from known parameters:
120//! ```rust,no_run
121//!   use page_hunter::{Book, Page};
122//!
123//!   let sheets: Vec<Page<u32>> = vec![
124//!     Page::new(&vec![1, 2], 0, 2, 5).unwrap(),
125//!     Page::new(&vec![3, 4], 1, 2, 5).unwrap(),
126//!   ];
127//!
128//!   let book: Book<u32> = Book::new(&sheets);
129//! ```
130//!
131//! On feature `serde` enabled, you can serialize and deserialize a [`Book`] as follows:
132//! ```rust,no_run
133//!   use page_hunter::{Book, Page};
134//!
135//!   let sheets: Vec<Page<u32>> = vec![
136//!     Page::new(&vec![1, 2], 0, 2, 5).unwrap(),
137//!     Page::new(&vec![3, 4], 1, 2, 5).unwrap(),
138//!   ];
139//!
140//!   let book: Book<u32> = Book::new(&sheets);
141//!
142//!   let serialized_book: String = serde_json::to_string(&book)
143//!     .unwrap_or_else(|error| {
144//!       panic!("Error serializing book model: {:?}", error);
145//!   });
146//!
147//!   let deserialized_book: Book<u32> = serde_json::from_str(&serialized_book)
148//!     .unwrap_or_else(|error| {
149//!       panic!("Error deserializing book model: {:?}", error);
150//!   });
151//! ```
152//!
153//! #### Generate OpenAPI schemas:
154//! On feature `utoipa` enabled, you can generate OpenAPI schemas for [`Page`] and [`Book`] models as follows:
155//! ```rust,no_run
156//!   use page_hunter::{Book, Page};
157//!   use utoipa::{OpenApi, ToSchema};
158//!   use serde::{Deserialize, Serialize};
159//!
160//!   #[derive(Clone, ToSchema)]
161//!   pub struct Person {
162//!     id: u16,
163//!     name: String,
164//!     last_name: String,
165//!     still_alive: bool,
166//!   }
167//!
168//!   pub type PeoplePage = Page<Person>;
169//!   pub type PeopleBook = Book<Person>;
170//!
171//!   #[derive(OpenApi)]
172//!   #[openapi(
173//!     components(schemas(PeoplePage, PeopleBook))
174//!   )]
175//!   pub struct ApiDoc;
176//! ```
177//!
178//! Take a look at the [examples](https://github.com/JMTamayo/page-hunter/tree/main/examples)  folder where you can find practical uses in REST API implementations with some web frameworks.
179//!
180//! #### Paginate records from a relational database with SQLx:
181//! To paginate records from a Postgres database:
182//! ```rust,no_run
183//!   use page_hunter::{Page, SQLxPagination};
184//!   use sqlx::postgres::{PgConnection, Postgres};
185//!   use sqlx::{Connection, FromRow, QueryBuilder};
186//!
187//!   #[tokio::main]
188//!   async fn main() {
189//!     #[derive(Clone, Debug, FromRow)]
190//!     pub struct Country {
191//!       id: i32,
192//!       name: String,
193//!     }
194//!
195//!     let mut conn: PgConnection = PgConnection::connect(
196//!       "postgres://username:password@localhost/db"
197//!     ).await.unwrap_or_else(|error| {
198//!       panic!("Error connecting to database: {:?}", error);
199//!     });
200//!
201//!     let query: QueryBuilder<Postgres> = QueryBuilder::new(
202//!       "SELECT * FROM db.geo.countries"
203//!     );
204//!
205//!     let page: Page<Country> =
206//!       query.paginate(&mut conn, 0, 10).await.unwrap_or_else(|error| {
207//!         panic!("Error paginating records: {:?}", error);
208//!     });
209//!   }
210//! ```
211//!
212//! Similar to using pagination for Postgres, [`SQLxPagination`] can be used for MySQL and SQLite. If you are working with a connection pool, you can [Acquire](https://docs.rs/sqlx/latest/sqlx/trait.Acquire.html)  a single connection before running [paginate](`SQLxPagination::paginate`).
213mod book;
214mod errors;
215mod page;
216mod pagination;
217mod results;
218
219pub use book::Book;
220pub use errors::{ErrorKind, PaginationError};
221pub use page::Page;
222pub use pagination::records::{RecordsPagination, bind_records, paginate_records};
223pub use results::PaginationResult;
224
225#[cfg(feature = "sqlx")]
226pub use pagination::sqlx::queries::SQLxPagination;