page_hunter/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
//! ***Page Hunter*** library is a Rust-based pagination tool that provides a way to manage and navigate through pages of data.
//! 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.
//!
//! The library also includes validation methods to ensure the integrity of the pagination data.
//! It's designed to be flexible and easy to integrate into any Rust project that requires pagination functionality standard data validation
//!
//! To use **page-hunter** from GitHub repository with specific version, set the dependency in Cargo.toml file as follows:
//!
//! ```ini
//! [dependencies]
//! page-hunter = { git = "https://github.com/JMTamayo/page-hunter.git", version = "0.3.0", features = ["serde"] }
//! ```
//!
//! You can depend on it via cargo by adding the following dependency to your `Cargo.toml` file:
//!
//! ```ini
//! [dependencies]
//! page-hunter = { version = "0.3.0", features = ["utoipa", "pg-sqlx"] }
//! ```
//!
//! ## CRATE FEATURES
//! - `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.
//! - `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.
//! - `pg-sqlx`: Add support for pagination with [SQLx](https://docs.rs/sqlx/0.8.1/sqlx/) for PostgreSQL database.
//! - `mysql-sqlx`: Add support for pagination with [SQLx](https://docs.rs/sqlx/0.8.1/sqlx/) for MySQL database.
//! - `sqlite-sqlx`: Add support for pagination with [SQLx](https://docs.rs/sqlx/0.8.1/sqlx/) for SQLIte database.
//!
//! ## BASIC OPERATION
//!
//! The **page-hunter** library provides two main models to manage pagination:
//! - [`Page`]: Represents a page of records with the current page, total pages, previous page, next page, and the items on the current page.
//! - [`Book`]: Represents a book of pages with a collection of [`Page`] instances.
//!
//! 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:
//!
//! #### Paginate records:
//! If you need to paginate records and get a specific [`Page`]:
//! ```rust,no_run
//! use page_hunter::*;
//!
//! let records: Vec<u32> = vec![1, 2, 3, 4, 5];
//! let page: usize = 0;
//! let size: usize = 2;
//!
//! let pagination_result: PaginationResult<Page<u32>> =
//! paginate_records(&records, page, size);
//! ```
//!
//! To create a new `Page` instance from known parameters:
//! ```rust,no_run
//! use page_hunter::*;
//!
//! let items: Vec<u32> = vec![1, 2];
//! let page: usize = 0;
//! let size: usize = 2;
//! let total_elements: usize = 5;
//!
//! let page_model_result: PaginationResult<Page<u32>> = Page::new(
//! &items,
//! page,
//! size,
//! total_elements,
//! );
//! ```
//!
//! On feature `serde` enabled, you can serialize and deserialize a [`Page`] as follows:
//! ```rust,no_run
//! use page_hunter::*;
//!
//! let items: Vec<u32> = vec![1, 2];
//! let page: usize = 0;
//! let size: usize = 2;
//! let total_elements: usize = 5;
//!
//! let page_model: Page<u32> = Page::new(
//! &items,
//! page,
//! size,
//! total_elements,
//! ).unwrap_or_else(|error| {
//! panic!("Error creating page model: {:?}", error);
//! });
//!
//! let serialized_page: String = serde_json::to_string(&page_model).unwrap_or_else(|error| {
//! panic!("Error serializing page model: {:?}", error);
//! });
//!
//! let deserialized_page: Page<u32> = serde_json::from_str(&serialized_page).unwrap_or_else(|error| {
//! panic!("Error deserializing page model: {:?}", error);
//! });
//! ```
//!
//! When you create a new [`Page`] instance from the constructor or deserialization, the following rules are validated for the fields on the page:
//! - ***pages*** must be equal to ***total*** divided by ***size*** rounded up. When ***size*** is 0, ***pages*** must be 1.
//! - ***page*** must be less than or equal to ***pages*** - 1.
//! - if ***page*** is less than ***pages*** - 1, ***items*** length must be equal to ***size***.
//! - if ***page*** is equal to ***pages*** - 1, ***total*** must be equal to (***pages*** - 1) * ***size*** + ***items*** length.
//! - ***previous_page*** must be equal to ***page*** - 1 if ***page*** is greater than 0, otherwise it must be [`None`].
//! - ***next_page*** must be equal to ***page*** + 1 if ***page*** is less than ***pages*** - 1, otherwise it must be [`None`].
//!
//! If any of these rules are violated, a [`PaginationError`] will be returned.
//!
//! #### Bind records:
//! If you need to bind records into a [`Book`] model:
//! ```rust,no_run
//! use page_hunter::*;
//!
//! let records: Vec<u32> = vec![1, 2, 3, 4, 5];
//! let size: usize = 2;
//!
//! let book_result: PaginationResult<Book<u32>> =
//! bind_records(&records, size);
//! ```
//!
//! To create a new [`Book`] instance from known parameters:
//! ```rust,no_run
//! use page_hunter::*;
//!
//! let sheets: Vec<Page<u32>> = vec![
//! Page::new(&vec![1, 2], 0, 2, 5).unwrap(),
//! Page::new(&vec![3, 4], 1, 2, 5).unwrap(),
//! ];
//!
//! let book: Book<u32> = Book::new(&sheets);
//! ```
//!
//! On feature `serde` enabled, you can serialize and deserialize a [`Book`] as follows:
//! ```rust,no_run
//! use page_hunter::*;
//!
//! let sheets: Vec<Page<u32>> = vec![
//! Page::new(&vec![1, 2], 0, 2, 5).unwrap(),
//! Page::new(&vec![3, 4], 1, 2, 5).unwrap(),
//! ];
//!
//! let book: Book<u32> = Book::new(&sheets);
//!
//! let serialized_book: String = serde_json::to_string(&book).unwrap_or_else(|error| {
//! panic!("Error serializing book model: {:?}", error);
//! });
//!
//! let deserialized_book: Book<u32> = serde_json::from_str(&serialized_book).unwrap_or_else(|error| {
//! panic!("Error deserializing book model: {:?}", error);
//! });
//! ```
//!
//! #### Generate OpenAPI schemas:
//! On feature `utoipa` enabled, you can generate OpenAPI schemas for [`Page`] and [`Book`] models as follows:
//!
//! ```rust,no_run
//! use page_hunter::*;
//! use utoipa::{OpenApi, ToSchema};
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Clone, ToSchema)]
//! pub struct Person {
//! id: u16,
//! name: String,
//! last_name: String,
//! still_alive: bool,
//! }
//!
//! pub type PeoplePage = Page<Person>;
//! pub type PeopleBook = Book<Person>;
//!
//! #[derive(OpenApi)]
//! #[openapi(
//! components(schemas(PeoplePage, PeopleBook))
//! )]
//! pub struct ApiDoc;
//! ```
//!
//! 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.
//!
//! #### Paginate records from a relational database with SQLx:
//! To paginate records from a PostgreSQL database:
//! ```rust,no_run
//! use page_hunter::*;
//! use sqlx::postgres::{PgPool, Postgres};
//! use sqlx::{FromRow, QueryBuilder};
//! use uuid::Uuid;
//!
//! #[tokio::main]
//! async fn main() {
//! #[derive(Clone, Debug, FromRow)]
//! pub struct Country {
//! id: Uuid,
//! name: String,
//! }
//!
//! let pool: PgPool = PgPool::connect(
//! "postgres://username:password@localhost/db"
//! ).await.unwrap_or_else(|error| {
//! panic!("Error connecting to database: {:?}", error);
//! });
//!
//! let query: QueryBuilder<Postgres> = QueryBuilder::new(
//! "SELECT * FROM db.geo.countries"
//! );
//!
//! let page: Page<Country> =
//! query.paginate(&pool, 0, 10).await.unwrap_or_else(|error| {
//! panic!("Error paginating records: {:?}", error);
//! });
//! }
//! ```
//!
//! To paginate records from a MySQL database:
//! ```rust,no_run
//! use page_hunter::*;
//! use sqlx::mysql::{MySqlPool, MySql};
//! use sqlx::{FromRow, QueryBuilder};
//! use uuid::Uuid;
//!
//! #[tokio::main]
//! async fn main() {
//! #[derive(Clone, Debug, FromRow)]
//! pub struct Country {
//! id: Uuid,
//! name: String,
//! }
//!
//! let pool: MySqlPool = MySqlPool::connect(
//! "mysql://username:password@localhost/db"
//! ).await.unwrap_or_else(|error| {
//! panic!("Error connecting to database: {:?}", error);
//! });
//!
//! let query: QueryBuilder<MySql> = QueryBuilder::new(
//! "SELECT * FROM countries"
//! );
//!
//! let page: Page<Country> =
//! query.paginate(&pool, 0, 10).await.unwrap_or_else(|error| {
//! panic!("Error paginating records: {:?}", error);
//! });
//! }
//! ```
//!
//! To paginate records from a SQLite database:
//! ```rust,no_run
//! use page_hunter::*;
//! use sqlx::sqlite::{SqlitePool, Sqlite};
//! use sqlx::{FromRow, QueryBuilder};
//! use uuid::Uuid;
//!
//! #[tokio::main]
//! async fn main() {
//! #[derive(Clone, Debug, FromRow)]
//! pub struct Country {
//! id: Uuid,
//! name: String,
//! }
//!
//! let pool: SqlitePool = SqlitePool::connect(
//! "sqlite://countries.db"
//! ).await.unwrap_or_else(|error| {
//! panic!("Error connecting to database: {:?}", error);
//! });
//!
//! let query: QueryBuilder<Sqlite> = QueryBuilder::new(
//! "SELECT * FROM countries"
//! );
//!
//! let page: Page<Country> =
//! query.paginate(&pool, 0, 10).await.unwrap_or_else(|error| {
//! panic!("Error paginating records: {:?}", error);
//! });
//! }
//! ```
//!
//! ## CONTRIBUTIONS
//! The ***Page Hunter*** project is open source and therefore any interested software developer can contribute to its improvement. To contribute, take a look at the following recommendations:
//!
//! - **Bug Reports**: If you find a bug, please create an issue detailing the problem, the steps to reproduce it, and the expected behavior.
//! - **Feature Requests**: If you have an idea for a new feature or an enhancement to an existing one, please create an issue describing your idea.
//! - **Pull Requests**: If you've fixed a bug or implemented a new feature, we'd love to see your work! Please submit a pull request. Make sure your code follows the existing style and all tests pass.
mod book;
mod errors;
mod page;
mod pagination;
mod results;
pub use book::Book;
pub use errors::{ErrorKind, PaginationError};
pub use page::Page;
pub use pagination::records::{bind_records, paginate_records};
pub use results::PaginationResult;
#[cfg(any(feature = "pg-sqlx", feature = "mysql-sqlx", feature = "sqlite-sqlx"))]
pub use pagination::sqlx::SQLxPagination;