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
use serde::{Deserialize, Serialize, Serializer};
use std::ops::{Deref, DerefMut};
use std::slice::{Iter, IterMut};
use std::vec::IntoIter;
use typed_builder::TypedBuilder;
#[cfg(feature = "fp-bindgen")]
use fp_bindgen::prelude::Serializable;
#[cfg(feature = "axum_06")]
use {
axum_06::http::{HeaderMap, HeaderValue},
axum_06::response::{IntoResponse, Response},
axum_06::Json,
};
/// HTTP header key that will be added on a list endpoint indicating whether
/// there are more results.
///
/// This header is optional and may not be present in every list endpoint.
///
/// Its values will be the literal string "true" if there are more results,
/// otherwise it will be "false" indicating that there are no more results. Any
/// other values should be treated the same as "false".
pub const HAS_MORE_RESULTS_KEY: &str = "FP-Has-More-Results";
/// HTTP header key that will be added on a list endpoint if it is known how
/// many results there are in total.
///
/// Note that this is an optional header. There is no guarantee that it will be
/// present in every list endpoint and can potentially be removed from any list
/// endpoint without notice.
///
/// The content of the value will be a unsigned 32 bit integer as a string.
pub const TOTAL_RESULTS_KEY: &str = "FP-Total-Results";
/// A PagedVec<T> wraps a regular Vec<T> but it also adds some metadata that
/// will set by the client. These values will parsed from the HTTP headers.
///
/// This type is not used directly as part of a response or request body, but
/// instead it is used by the fiberplane-api-client for endpoints that return
/// multiple items.
#[derive(Debug, PartialEq)]
pub struct PagedVec<T> {
pub inner: Vec<T>,
pub has_more_results: bool,
pub total_results: Option<u32>,
}
impl<T> Deref for PagedVec<T> {
type Target = Vec<T>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for PagedVec<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T> PagedVec<T> {
pub fn page_amount(&self, pagination: &Pagination) -> Option<u32> {
if !self.has_more_results {
Some(pagination.page)
} else {
self.total_results.map(|results| results / pagination.limit)
}
}
#[inline]
pub fn iter(&self) -> Iter<'_, T> {
self.inner.iter()
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
self.inner.iter_mut()
}
#[inline]
pub fn into_inner(self) -> Vec<T> {
self.inner
}
/// Maps this `PagedVec<T>` to a `PagedVec<B>`, while maintaining the same
/// values for `has_more_results` and `total_results`.
pub fn map<B, F>(self, f: F) -> PagedVec<B>
where
F: FnMut(T) -> B,
{
PagedVec {
inner: self.inner.into_iter().map(f).collect(),
has_more_results: self.has_more_results,
total_results: self.total_results,
}
}
#[inline]
pub fn into<B: From<T>>(self) -> PagedVec<B> {
self.map(|value| Into::<B>::into(value))
}
#[cfg(feature = "axum_06")]
pub fn unpack(self) -> (Vec<T>, HeaderMap) {
let has_more_results = &self.has_more_results.to_string();
let has_more_results = HeaderValue::from_str(has_more_results).unwrap(); // Safe because .to_string does not result in invalid ascii
let mut map = HeaderMap::new();
map.insert(HAS_MORE_RESULTS_KEY, has_more_results);
if let Some(total_results) = self.total_results {
let total_results = &total_results.to_string();
let total_results = HeaderValue::from_str(total_results).unwrap(); // Safe because .to_string does not result in invalid ascii
map.insert(TOTAL_RESULTS_KEY, total_results);
}
(self.inner, map)
}
}
#[cfg(feature = "axum_06")]
impl<T> IntoResponse for PagedVec<T>
where
T: Serialize,
Json<Vec<T>>: IntoResponse,
{
fn into_response(self) -> Response {
let (results, headers) = self.unpack();
(headers, Json(results)).into_response()
}
}
impl<T> IntoIterator for PagedVec<T> {
type Item = T;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl<T: Serialize> Serialize for PagedVec<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.inner.serialize(serializer)
}
}
/// A struct that represents the pagination of a list endpoint.
///
/// Some list endpoints support pagination by passing in querystring parameters,
/// which are `page` and `limit`. This Pagination object is used to be used
/// within api client when making a request.
///
/// Note that not all endpoints support pagination and some endpoints might
/// ignore values that are too high.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::paging")
)]
#[non_exhaustive]
pub struct Pagination {
#[serde(
default = "Pagination::default_page",
deserialize_with = "crate::deserialize_u32"
)]
pub page: u32,
#[serde(
default = "Pagination::default_limit",
deserialize_with = "crate::deserialize_u32"
)]
pub limit: u32,
}
impl Pagination {
#[inline]
fn default_page() -> u32 {
0
}
#[inline]
fn default_limit() -> u32 {
200
}
/// The number of items to return.
pub fn limit(&self) -> i64 {
self.limit as i64
}
/// The number of items to skip. This is the same as `page * limit`.
pub fn offset(&self) -> i64 {
self.page as i64 * self.limit as i64
}
/// Create a pagination that effectively fetches every item (since limit is
/// set to the max value).
///
/// Note that some endpoints might ignore setting this value this high.
pub fn max() -> Self {
Self {
page: 0,
limit: u32::MAX,
}
}
}
impl Default for Pagination {
fn default() -> Self {
Self {
page: Pagination::default_page(),
limit: Pagination::default_limit(),
}
}
}