Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions crates/handlers/src/admin/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// Generated code from schemars violates this rule
#![allow(clippy::str_to_string)]

use std::num::NonZeroUsize;
use std::{borrow::Cow, num::NonZeroUsize};

use aide::OperationIo;
use axum::{
Expand Down Expand Up @@ -64,6 +64,34 @@ impl std::ops::Deref for UlidPathParam {
/// The default page size if not specified
const DEFAULT_PAGE_SIZE: usize = 10;

#[derive(Deserialize, JsonSchema, Clone, Copy, Default, Debug)]
pub enum IncludeCount {
/// Include the total number of items (default)
#[default]
#[serde(rename = "true")]
True,

/// Do not include the total number of items
#[serde(rename = "false")]
False,

/// Only include the total number of items, skip the items themselves
#[serde(rename = "only")]
Only,
}

impl IncludeCount {
pub(crate) fn add_to_base(self, base: &str) -> Cow<'_, str> {
let separator = if base.contains('?') { '&' } else { '?' };
match self {
// This is the default, don't add anything
Self::True => Cow::Borrowed(base),
Self::False => format!("{base}{separator}count=false").into(),
Self::Only => format!("{base}{separator}count=only").into(),
}
}
}

#[derive(Deserialize, JsonSchema, Clone, Copy)]
struct PaginationParams {
/// Retrieve the items before the given ID
Expand All @@ -83,6 +111,10 @@ struct PaginationParams {
/// Retrieve the last N items
#[serde(rename = "page[last]")]
last: Option<NonZeroUsize>,

/// Include the total number of items. Defaults to `true`.
#[serde(rename = "count")]
include_count: Option<IncludeCount>,
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -107,7 +139,7 @@ impl IntoResponse for PaginationRejection {
/// An extractor for pagination parameters in the query string
#[derive(OperationIo, Debug, Clone, Copy)]
#[aide(input_with = "Query<PaginationParams>")]
pub struct Pagination(pub mas_storage::Pagination);
pub struct Pagination(pub mas_storage::Pagination, pub IncludeCount);

impl<S: Send + Sync> FromRequestParts<S> for Pagination {
type Rejection = PaginationRejection;
Expand All @@ -130,11 +162,14 @@ impl<S: Send + Sync> FromRequestParts<S> for Pagination {
(None, Some(last)) => (PaginationDirection::Backward, last.into()),
};

Ok(Self(mas_storage::Pagination {
before: params.before,
after: params.after,
direction,
count,
}))
Ok(Self(
mas_storage::Pagination {
before: params.before,
after: params.after,
direction,
count,
},
params.include_count.unwrap_or_default(),
))
}
}
51 changes: 42 additions & 9 deletions crates/handlers/src/admin/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ struct PaginationLinks {
self_: String,

/// The link to the first page of results
first: String,
#[serde(skip_serializing_if = "Option::is_none")]
first: Option<String>,

/// The link to the last page of results
last: String,
#[serde(skip_serializing_if = "Option::is_none")]
last: Option<String>,

/// The link to the next page of results
///
Expand All @@ -42,17 +44,26 @@ struct PaginationLinks {
#[derive(Serialize, JsonSchema)]
struct PaginationMeta {
/// The total number of results
count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
count: Option<usize>,
}

impl PaginationMeta {
fn is_empty(&self) -> bool {
self.count.is_none()
}
}

/// A top-level response with a page of resources
#[derive(Serialize, JsonSchema)]
pub struct PaginatedResponse<T> {
/// Response metadata
#[serde(skip_serializing_if = "PaginationMeta::is_empty")]
meta: PaginationMeta,

/// The list of resources
data: Vec<SingleResource<T>>,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Vec<SingleResource<T>>>,

/// Related links
links: PaginationLinks,
Expand Down Expand Up @@ -87,16 +98,22 @@ fn url_with_pagination(base: &str, pagination: Pagination) -> String {
}

impl<T: Resource> PaginatedResponse<T> {
pub fn new(
pub fn for_page(
page: mas_storage::Page<T>,
current_pagination: Pagination,
count: usize,
count: Option<usize>,
base: &str,
) -> Self {
let links = PaginationLinks {
self_: url_with_pagination(base, current_pagination),
first: url_with_pagination(base, Pagination::first(current_pagination.count)),
last: url_with_pagination(base, Pagination::last(current_pagination.count)),
first: Some(url_with_pagination(
base,
Pagination::first(current_pagination.count),
)),
last: Some(url_with_pagination(
base,
Pagination::last(current_pagination.count),
)),
next: page.has_next_page.then(|| {
url_with_pagination(
base,
Expand Down Expand Up @@ -125,7 +142,23 @@ impl<T: Resource> PaginatedResponse<T> {

Self {
meta: PaginationMeta { count },
data,
data: Some(data),
links,
}
}

pub fn for_count_only(count: usize, base: &str) -> Self {
let links = PaginationLinks {
self_: base.to_owned(),
first: None,
last: None,
next: None,
prev: None,
};

Self {
meta: PaginationMeta { count: Some(count) },
data: None,
links,
}
}
Expand Down
Loading
Loading