This commit is contained in:
parent
d6555edc29
commit
00bb6f152d
8 changed files with 332 additions and 11 deletions
|
@ -83,3 +83,168 @@ pub fn api_error(msg: &str) -> ApiError {
|
|||
"error": msg
|
||||
}))
|
||||
}
|
||||
|
||||
/// A `Pager` that manages paginated items, with the ability to handle incomplete data.
|
||||
pub struct Pager<T> {
|
||||
inner: Vec<T>,
|
||||
pub items_per_page: u64,
|
||||
complete_at: Option<u64>,
|
||||
}
|
||||
|
||||
impl<T> Pager<T> {
|
||||
/// Creates a new `Pager` instance with the given items and items per page.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `items` - A vector of items to paginate.
|
||||
/// * `per_page` - Number of items per page.
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `Pager` instance.
|
||||
#[must_use]
|
||||
pub const fn new(items: Vec<T>, per_page: u64) -> Self {
|
||||
Self {
|
||||
inner: items,
|
||||
items_per_page: per_page,
|
||||
complete_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Pager` instance for an incomplete dataset, starting from a specific page.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `items` - A vector of items to paginate.
|
||||
/// * `per_page` - Number of items per page.
|
||||
/// * `at_page` - The page where data starts, meaning there are no pages before it.
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `Pager` instance.
|
||||
#[must_use]
|
||||
pub const fn new_incomplete(items: Vec<T>, per_page: u64, at_page: u64) -> Self {
|
||||
Self {
|
||||
inner: items,
|
||||
items_per_page: per_page,
|
||||
complete_at: Some(at_page),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the offset for the given page number.
|
||||
///
|
||||
/// This method considers whether the pager has an incomplete dataset and adjusts the offset accordingly.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `page` - The page number for which the offset is calculated.
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated offset.
|
||||
#[must_use]
|
||||
pub const fn offset(&self, page: u64) -> u64 {
|
||||
if let Some(incomplete) = self.complete_at {
|
||||
let page = page - incomplete;
|
||||
return self.items_per_page * page;
|
||||
}
|
||||
|
||||
self.items_per_page * (page - 1)
|
||||
}
|
||||
|
||||
/// Retrieves the items for a specific page.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `page` - The page number to retrieve.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if trying to access a page before the `complete_at` page in an incomplete pager.
|
||||
///
|
||||
/// # Returns
|
||||
/// A vector of items on the requested page.
|
||||
#[must_use]
|
||||
pub fn page(&self, page: u64) -> Vec<&T> {
|
||||
if let Some(incomplete) = self.complete_at {
|
||||
assert!(
|
||||
page >= incomplete,
|
||||
"Tried to access illegal page on incomplete Pager"
|
||||
);
|
||||
}
|
||||
|
||||
self.inner
|
||||
.iter()
|
||||
.skip(self.offset(page).try_into().unwrap())
|
||||
.take(self.items_per_page.try_into().unwrap())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A `GeneratedPager` is a paginated generator that fetches items dynamically from a generator function.
|
||||
pub struct GeneratedPager<T, G, I>
|
||||
where
|
||||
G: Fn(I, u64, u64) -> futures::future::BoxFuture<'static, Vec<T>>,
|
||||
{
|
||||
generator: G,
|
||||
pub items_per_page: u64,
|
||||
_marker: std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T, G, I> GeneratedPager<T, G, I>
|
||||
where
|
||||
G: Fn(I, u64, u64) -> futures::future::BoxFuture<'static, Vec<T>>,
|
||||
{
|
||||
/// Creates a new `GeneratedPager` instance with the provided generator and pagination settings.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `generator` - A function that generates a page of items based on the input and pagination settings.
|
||||
/// * `items_per_page` - Number of items per page.
|
||||
///
|
||||
/// # Generator
|
||||
/// The generator function should take the following arguments:
|
||||
/// * `input` - Generic input to the generator
|
||||
/// * `offset` - Offset value
|
||||
/// * `limit` - Limit value (items per page)
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `GeneratedPager` instance.
|
||||
pub const fn new(generator: G, items_per_page: u64) -> Self {
|
||||
Self {
|
||||
generator,
|
||||
items_per_page,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the offset for the given page number.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `page` - The page number for which the offset is calculated.
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated offset.
|
||||
pub const fn offset(&self, page: u64) -> u64 {
|
||||
self.items_per_page * (page - 1)
|
||||
}
|
||||
|
||||
/// Asynchronously retrieves the items for a specific page from the generator.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `page` - The page number to retrieve.
|
||||
/// * `input` - The input that is passed to the generator.
|
||||
///
|
||||
/// # Returns
|
||||
/// A vector of items on the requested page.
|
||||
pub async fn page(&self, page: u64, input: I) -> Vec<T> {
|
||||
let offset = self.offset(page);
|
||||
(self.generator)(input, offset, self.items_per_page).await
|
||||
}
|
||||
|
||||
/// Converts the `GeneratedPager` into a regular `Pager` for a given page of items.
|
||||
///
|
||||
/// This method allows you to use a `GeneratedPager` like a regular `Pager`, with a pre-generated dataset.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `page` - The page number to retrieve.
|
||||
/// * `input` - The input to pass to the generator function.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Pager` instance containing the requested page of items.
|
||||
pub async fn pager(&self, page: u64, input: I) -> Pager<T> {
|
||||
let content = self.page(page, input).await;
|
||||
Pager::new_incomplete(content, self.items_per_page, page)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ pub struct DataResponse {
|
|||
|
||||
impl DataResponse {
|
||||
#[must_use]
|
||||
pub fn new(data: Vec<u8>, content_type: String, cache_duration: Option<u64>) -> Self {
|
||||
pub const fn new(data: Vec<u8>, content_type: String, cache_duration: Option<u64>) -> Self {
|
||||
Self {
|
||||
data,
|
||||
content_type,
|
||||
|
|
|
@ -4,6 +4,7 @@ use rocket::{
|
|||
};
|
||||
|
||||
/// Represents contextual information about an HTTP request.
|
||||
#[derive(Default)]
|
||||
pub struct RequestContext {
|
||||
/// A flag indicating if the request is an HTMX request.
|
||||
///
|
||||
|
@ -21,9 +22,3 @@ impl<'r> FromRequest<'r> for RequestContext {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RequestContext {
|
||||
fn default() -> Self {
|
||||
Self { is_htmx: false }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ pub fn respond_json(json: &serde_json::Value) -> StringResponse {
|
|||
/// # Returns
|
||||
/// A `StringResponse` with status `200 OK`, content type `text/html`, and the HTML content as the body.
|
||||
#[must_use]
|
||||
pub fn respond_html(html: String) -> StringResponse {
|
||||
pub const fn respond_html(html: String) -> StringResponse {
|
||||
(Status::Ok, (ContentType::HTML, html))
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ pub fn respond_html(html: String) -> StringResponse {
|
|||
/// # Returns
|
||||
/// A `StringResponse` with status `200 OK`, content type `text/javascript`, and the JS content as the body.
|
||||
#[must_use]
|
||||
pub fn respond_script(script: String) -> StringResponse {
|
||||
pub const fn respond_script(script: String) -> StringResponse {
|
||||
(Status::Ok, (ContentType::JavaScript, script))
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue