1use std::{borrow::Cow, collections::BTreeSet, fmt::Display};
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9use super::{
10 developers::DeveloperId,
11 endpoint::Endpoint,
12 engines::EngineId,
13 error::BodyError,
14 games::{Games, GamesBuilder, GamesBuilderError, GamesSorting},
15 gametypes::GameTypeId,
16 genres::GenreId,
17 platforms::PlatformId,
18 publishers::PublisherId,
19 query_params::QueryParams,
20 regions::RegionId,
21 users::UserId,
22 Direction, Pageable,
23};
24
25#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
27pub enum SeriesEmbeds {
28 Moderators,
30}
31
32#[derive(Debug, Serialize, Clone, Copy)]
34#[serde(rename_all = "kebab-case")]
35pub enum SeriesSorting {
36 #[serde(rename = "name.int")]
38 NameInternational,
39 #[serde(rename = "name.jap")]
41 NameJapanese,
42 Abbreviation,
44 Created,
46}
47
48#[derive(Debug, Error)]
50pub enum SeriesGamesBuilderError {
51 #[error("{0} must be initialized")]
53 UninitializedField(&'static str),
54 #[error(transparent)]
56 Inner(#[from] GamesBuilderError),
57}
58
59#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
61pub struct SeriesId<'a>(Cow<'a, str>);
62
63impl<'a> SeriesId<'a> {
64 pub fn new<T>(id: T) -> Self
66 where
67 T: Into<Cow<'a, str>>,
68 {
69 Self(id.into())
70 }
71}
72
73impl<'a, T> From<T> for SeriesId<'a>
74where
75 T: Into<Cow<'a, str>>,
76{
77 fn from(value: T) -> Self {
78 Self::new(value)
79 }
80}
81
82impl Display for SeriesId<'_> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}", &self.0)
85 }
86}
87
88#[derive(Default, Debug, Builder, Serialize, Clone)]
90#[builder(default, setter(into, strip_option))]
91#[serde(rename_all = "kebab-case")]
92pub struct ListSeries<'a> {
93 #[doc = r"When given, performs a fuzzy search across all series names and abbreviations."]
94 name: Option<Cow<'a, str>>,
95 #[doc = r"When given, performs an exact-match search for `abbreviation`."]
96 abbreviation: Option<Cow<'a, str>>,
97 #[doc = r"When given, only return series moderated by [`UserId`]"]
98 moderator: Option<UserId<'a>>,
99 #[doc = r"Sorting options for results."]
100 orderby: Option<SeriesSorting>,
101 #[doc = r"Sort direction"]
102 direction: Option<Direction>,
103 #[builder(setter(name = "_embed"), private)]
104 #[serde(serialize_with = "super::utils::serialize_as_csv")]
105 #[serde(skip_serializing_if = "BTreeSet::is_empty")]
106 embed: BTreeSet<SeriesEmbeds>,
107}
108
109#[derive(Debug, Builder, Clone)]
111#[builder(setter(into, strip_option))]
112pub struct Series<'a> {
113 #[doc = r"Series ID or abbreviation"]
114 id: SeriesId<'a>,
115}
116
117#[derive(Debug, Clone)]
119pub struct SeriesGames<'a> {
120 id: SeriesId<'a>,
121 inner: Games<'a>,
122}
123
124#[derive(Default, Clone)]
126pub struct SeriesGamesBuilder<'a> {
127 id: Option<SeriesId<'a>>,
129 inner: GamesBuilder<'a>,
130}
131
132impl ListSeries<'_> {
133 pub fn builder<'a>() -> ListSeriesBuilder<'a> {
135 ListSeriesBuilder::default()
136 }
137}
138
139impl ListSeriesBuilder<'_> {
140 pub fn embed(&mut self, embed: SeriesEmbeds) -> &mut Self {
142 self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
143 self
144 }
145
146 pub fn embeds<I>(&mut self, iter: I) -> &mut Self
148 where
149 I: Iterator<Item = SeriesEmbeds>,
150 {
151 self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
152 self
153 }
154}
155
156impl Series<'_> {
157 pub fn builder<'a>() -> SeriesBuilder<'a> {
159 SeriesBuilder::default()
160 }
161}
162
163impl SeriesGames<'_> {
164 pub fn builder<'a>() -> SeriesGamesBuilder<'a> {
166 SeriesGamesBuilder::default()
167 }
168}
169
170impl<'a> SeriesGamesBuilder<'a> {
171 pub fn new() -> Self {
173 Self::default()
174 }
175
176 pub fn id<S>(&mut self, id: S) -> &mut Self
178 where
179 S: Into<SeriesId<'a>>,
180 {
181 self.id = Some(id.into());
182 self
183 }
184
185 #[doc = r"Performs a fuzzy search across game names and abbreviations."]
186 pub fn name<S>(&mut self, value: S) -> &mut Self
187 where
188 S: Into<Cow<'a, str>>,
189 {
190 self.inner.name(value);
191 self
192 }
193
194 #[doc = r"Perform an exact-match search for this abbreviation."]
195 pub fn abbreviation<S>(&mut self, value: S) -> &mut Self
196 where
197 S: Into<Cow<'a, str>>,
198 {
199 self.inner.abbreviation(value);
200 self
201 }
202
203 #[doc = r"Restrict results to games released in the given year."]
204 pub fn released(&mut self, value: i64) -> &mut Self {
205 self.inner.released(value);
206 self
207 }
208
209 #[doc = r"Restrict results to the given game type."]
210 pub fn gametype<S>(&mut self, value: S) -> &mut Self
211 where
212 S: Into<GameTypeId<'a>>,
213 {
214 self.inner.gametype(value);
215 self
216 }
217
218 #[doc = r"Restrict results to the given platform."]
219 pub fn platform<S>(&mut self, value: S) -> &mut Self
220 where
221 S: Into<PlatformId<'a>>,
222 {
223 self.inner.platform(value);
224 self
225 }
226
227 #[doc = r"Restrict results to the given region."]
228 pub fn region<S>(&mut self, value: S) -> &mut Self
229 where
230 S: Into<RegionId<'a>>,
231 {
232 self.inner.region(value);
233 self
234 }
235
236 #[doc = r"Restrict results to the given genre."]
237 pub fn genre<S>(&mut self, value: S) -> &mut Self
238 where
239 S: Into<GenreId<'a>>,
240 {
241 self.inner.genre(value);
242 self
243 }
244
245 #[doc = r"Restrict results to the given engine."]
246 pub fn engine<S>(&mut self, value: S) -> &mut Self
247 where
248 S: Into<EngineId<'a>>,
249 {
250 self.inner.engine(value);
251 self
252 }
253
254 #[doc = r"Restrict results to the given developer."]
255 pub fn developer<S>(&mut self, value: S) -> &mut Self
256 where
257 S: Into<DeveloperId<'a>>,
258 {
259 self.inner.developer(value);
260 self
261 }
262
263 #[doc = r"Restrict results to the given publisher."]
264 pub fn publisher<S>(&mut self, value: S) -> &mut Self
265 where
266 S: Into<PublisherId<'a>>,
267 {
268 self.inner.publisher(value);
269 self
270 }
271
272 #[doc = r"Only return games moderated by the given user."]
273 pub fn moderator<S>(&mut self, value: S) -> &mut Self
274 where
275 S: Into<UserId<'a>>,
276 {
277 self.inner.moderator(value);
278 self
279 }
280
281 #[doc = r"Enable bulk access."]
282 pub fn bulk(&mut self, value: bool) -> &mut Self {
283 self.inner.bulk(value);
284 self
285 }
286
287 #[doc = r"Sorting options for results."]
288 pub fn orderby(&mut self, value: GamesSorting) -> &mut Self {
289 self.inner.orderby(value);
290 self
291 }
292
293 #[doc = r"Sort direction."]
294 pub fn direction(&mut self, value: Direction) -> &mut Self {
295 self.inner.direction(value);
296 self
297 }
298
299 pub fn build(&self) -> Result<SeriesGames<'a>, SeriesGamesBuilderError> {
301 let inner = self.inner.build()?;
302 Ok(SeriesGames {
303 id: self
304 .id
305 .as_ref()
306 .cloned()
307 .ok_or(SeriesGamesBuilderError::UninitializedField("id"))?,
308 inner,
309 })
310 }
311}
312
313impl SeriesEmbeds {
314 fn as_str(&self) -> &'static str {
315 match self {
316 SeriesEmbeds::Moderators => "moderators",
317 }
318 }
319}
320
321impl Default for SeriesSorting {
322 fn default() -> Self {
323 Self::NameInternational
324 }
325}
326
327impl Endpoint for ListSeries<'_> {
328 fn endpoint(&self) -> Cow<'static, str> {
329 "/series".into()
330 }
331
332 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
333 QueryParams::with(self)
334 }
335}
336
337impl Endpoint for Series<'_> {
338 fn endpoint(&self) -> Cow<'static, str> {
339 format!("/series/{}", self.id).into()
340 }
341}
342
343impl Endpoint for SeriesGames<'_> {
344 fn endpoint(&self) -> Cow<'static, str> {
345 format!("/series/{}/games", self.id).into()
346 }
347
348 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
349 QueryParams::with(&self.inner)
350 }
351}
352
353impl From<&SeriesEmbeds> for &'static str {
354 fn from(value: &SeriesEmbeds) -> Self {
355 value.as_str()
356 }
357}
358
359impl Pageable for ListSeries<'_> {}
360
361impl Pageable for SeriesGames<'_> {}