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;