1use std::fmt;
2use std::marker::PhantomData;
3use std::vec::IntoIter;
4
5use serde::de::DeserializeOwned;
6
7use crate::request::{Filter, RequestBuilder, Route};
8use crate::response::BodyError;
9use crate::types::List;
10use crate::{Client, Error};
11
12pub trait Paginate<'a>: private::Sealed {
14 type Output;
15
16 fn paged(&'a self) -> Paginator<'a, Self::Output>;
38}
39
40pub struct Paginator<'a, T> {
41 http: &'a Client,
42 route: Route,
43 filter: Filter,
44 state: State,
45 phantom: PhantomData<T>,
46}
47
48#[derive(Debug)]
50pub enum PaginateError {
51 Request(Error),
52 Body(BodyError),
53}
54
55impl fmt::Display for PaginateError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::Request(err) => err.fmt(f),
59 Self::Body(err) => err.fmt(f),
60 }
61 }
62}
63
64impl std::error::Error for PaginateError {}
65
66#[derive(Debug)]
67pub struct Page<T>(List<T>);
68
69impl<T> std::ops::Deref for Page<T> {
70 type Target = List<T>;
71
72 fn deref(&self) -> &Self::Target {
73 &self.0
74 }
75}
76
77impl<T> IntoIterator for Page<T> {
78 type Item = T;
79 type IntoIter = IntoIter<T>;
80
81 fn into_iter(self) -> Self::IntoIter {
82 self.0.data.into_iter()
83 }
84}
85
86enum State {
87 Start,
88 Next { offset: u32, limit: u32 },
89 Completed,
90}
91
92impl<'a, T: DeserializeOwned + Unpin> Paginator<'a, T> {
93 pub(crate) fn new(http: &'a Client, route: Route, filter: Option<Filter>) -> Self {
94 Self {
95 http,
96 route,
97 filter: filter.unwrap_or_default(),
98 state: State::Start,
99 phantom: PhantomData,
100 }
101 }
102
103 pub async fn next(&mut self) -> Result<Option<Page<T>>, PaginateError> {
104 let state = std::mem::replace(&mut self.state, State::Completed);
105
106 let filter = self.filter.clone();
107
108 let filter = match state {
109 State::Start => filter,
110 State::Next { offset, limit } => filter.offset((offset + limit) as usize),
111 State::Completed => return Ok(None),
112 };
113
114 let req = RequestBuilder::from_route(&self.route)
115 .filter(filter)
116 .empty()
117 .map_err(PaginateError::Request)?;
118
119 let list = self
120 .http
121 .request::<List<T>>(req)
122 .await
123 .map_err(PaginateError::Request)?
124 .data()
125 .await
126 .map_err(PaginateError::Body)?;
127
128 if list.data.is_empty() {
129 return Ok(None);
130 }
131
132 self.state = State::Next {
133 offset: list.offset,
134 limit: list.limit,
135 };
136
137 Ok(Some(Page(list)))
138 }
139}
140
141mod private {
142 use crate::request::files;
143 use crate::request::games;
144 use crate::request::mods;
145 use crate::request::user;
146
147 pub trait Sealed {}
148
149 impl Sealed for games::GetGames<'_> {}
150 impl Sealed for games::tags::GetGameTags<'_> {}
151
152 impl Sealed for mods::GetMods<'_> {}
153 impl Sealed for mods::comments::GetModComments<'_> {}
154 impl Sealed for mods::dependencies::GetModDependencies<'_> {}
155 impl Sealed for mods::events::GetModEvents<'_> {}
156 impl Sealed for mods::events::GetModsEvents<'_> {}
157 impl Sealed for mods::stats::GetModsStats<'_> {}
158 impl Sealed for mods::tags::GetModTags<'_> {}
159
160 impl Sealed for files::GetFiles<'_> {}
161 impl Sealed for files::multipart::GetMultipartUploadParts<'_> {}
162 impl Sealed for files::multipart::GetMultipartUploadSessions<'_> {}
163
164 impl Sealed for user::GetMutedUsers<'_> {}
165 impl Sealed for user::GetUserFiles<'_> {}
166 impl Sealed for user::GetUserGames<'_> {}
167 impl Sealed for user::GetUserMods<'_> {}
168 impl Sealed for user::GetUserRatings<'_> {}
169 impl Sealed for user::GetUserSubscriptions<'_> {}
170}