eureka_mmanager_core/data_pulls/
results.rs

1use std::num::TryFromIntError;
2
3use mangadex_api_schema_rust::v5::Results;
4use mangadex_api_types_rust::{ResponseType, ResultType};
5use serde::{Deserialize, Serialize};
6#[cfg(feature = "stream")]
7use tokio_stream::{Stream, StreamExt};
8
9/// The result of [`Paginate::paginate`] or [`AsyncPaginate::paginate`].
10#[derive(Debug, Serialize, Deserialize, Clone)]
11pub struct Collection<T> {
12    pub data: Vec<T>,
13    pub offset: usize,
14    pub limit: usize,
15    pub total: usize,
16}
17
18impl<T> Collection<T> {
19    /// Transform this into an [`Results`].
20    ///
21    /// __Why it returns a [`Result<Results<T>, TryFromIntError>`]?__
22    ///
23    /// Since [`Results::offset`], [`Results::total`], [`Results::limit`], [`Results::total`] is not [`usize`], we need to transform them into an [`u32`].
24    pub fn into_results(self) -> Result<Results<T>, TryFromIntError> {
25        self.try_into()
26    }
27}
28
29impl<T> TryFrom<Collection<T>> for Results<T> {
30    type Error = TryFromIntError;
31    fn try_from(value: Collection<T>) -> Result<Self, Self::Error> {
32        Ok(Self {
33            result: ResultType::Ok,
34            response: ResponseType::Collection,
35            data: value.data,
36            limit: value.limit.try_into()?,
37            offset: value.offset.try_into()?,
38            total: value.total.try_into()?,
39        })
40    }
41}
42
43/// Paginate allows you to get portion of the underlying stream.
44///
45/// This is heavily inspired by the [SQL offset limit system](https://www.postgresql.org/docs/current/queries-limit.html).
46///
47/// If you want an synchronous version, use [`Paginate`] instead.
48///
49/// Note: If the underlying [`Stream::size_hint`] doesn't give the total, it will collect the stream into a [`Vec`] and call [`Paginate::paginate`] after.
50pub trait AsyncPaginate<T> {
51    fn paginate(
52        self,
53        offset: usize,
54        limit: usize,
55    ) -> impl std::future::Future<Output = Collection<T>> + Send;
56}
57
58/// Paginate allows you to get portion of the underlying [`Vec`] (unfortunalty).
59///
60/// This is heavily inspired by the [SQL offset limit system](https://www.postgresql.org/docs/current/queries-limit.html).
61///
62/// If you want an asynchronous version, use [`Paginate`] instead.
63pub trait Paginate<T> {
64    fn paginate(self, offset: usize, limit: usize) -> Collection<T>;
65}
66
67impl<T> Paginate<T> for Vec<T> {
68    fn paginate(self, offset: usize, limit: usize) -> Collection<T> {
69        let total = self.len();
70        let data = self
71            .into_iter()
72            .skip(offset)
73            .take(limit)
74            .collect::<Vec<_>>();
75        Collection {
76            total,
77            data,
78            offset,
79            limit,
80        }
81    }
82}
83
84#[cfg(feature = "stream")]
85#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
86impl<S, T> AsyncPaginate<T> for S
87where
88    S: Stream<Item = T> + Send,
89    T: Send,
90{
91    async fn paginate(self, offset: usize, limit: usize) -> Collection<T> {
92        let stream = Box::pin(self);
93        let (_, pre_total) = stream.size_hint();
94        if let Some(total) = pre_total {
95            let data = stream.skip(offset).take(limit).collect::<Vec<_>>().await;
96            Collection {
97                data,
98                offset,
99                limit,
100                total,
101            }
102        } else {
103            stream.collect::<Vec<_>>().await.paginate(offset, limit)
104        }
105    }
106}