1use std::{borrow::Cow, collections::BTreeSet, fmt::Display};
6
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use super::{
11 categories::CategoryEmbeds, developers::DeveloperId, endpoint::Endpoint, engines::EngineId,
12 error::BodyError, gametypes::GameTypeId, genres::GenreId, leaderboards::LeaderboardEmbeds,
13 platforms::PlatformId, publishers::PublisherId, query_params::QueryParams, regions::RegionId,
14 users::UserId, CategoriesSorting, Direction, Pageable, VariablesSorting,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
21pub enum GameEmbeds {
22 Levels,
24 Categories,
26 Moderators,
28 Gametypes,
30 Platforms,
32 Regions,
34 Genres,
36 Engines,
38 Developers,
40 Publishers,
42 Variables,
44}
45
46#[derive(Debug, Serialize, Clone, Copy)]
48#[serde(rename_all = "kebab-case")]
49pub enum GamesSorting {
50 #[serde(rename = "name.int")]
52 NameInternational,
53 #[serde(rename = "name.jap")]
55 NameJapanese,
56 Abbreviation,
58 Released,
60 Created,
62 Similarity,
65}
66
67#[derive(Debug, Serialize, Clone, Copy)]
70#[serde(rename_all = "kebab-case")]
71pub enum LevelsSorting {
72 Name,
74 Pos,
76}
77
78#[derive(Debug, Serialize, Clone, Copy)]
81#[serde(rename_all = "kebab-case")]
82pub enum LeaderboardScope {
83 FullGame,
85 Levels,
87 All,
89}
90
91#[derive(Debug, Error)]
93pub enum GameDerivedGamesBuilderError {
94 #[error("{0} must be initialized")]
96 UninitializedField(&'static str),
97 #[error(transparent)]
99 Inner(#[from] GamesBuilderError),
100}
101
102#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
104pub struct GameId<'a>(Cow<'a, str>);
105
106impl<'a> GameId<'a> {
107 pub fn new<T>(id: T) -> Self
109 where
110 T: Into<Cow<'a, str>>,
111 {
112 Self(id.into())
113 }
114}
115
116impl<'a, T> From<T> for GameId<'a>
117where
118 T: Into<Cow<'a, str>>,
119{
120 fn from(value: T) -> Self {
121 Self::new(value)
122 }
123}
124
125impl Display for GameId<'_> {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(f, "{}", &self.0)
128 }
129}
130
131#[derive(Default, Debug, Builder, Serialize, Clone)]
133#[builder(default, setter(into, strip_option))]
134#[serde(rename_all = "kebab-case")]
135pub struct Games<'a> {
136 #[doc = r"Performs a fuzzy search across game names and abbreviations."]
137 name: Option<Cow<'a, str>>,
138 #[doc = r"Perform an exact-match search for this abbreviation."]
139 abbreviation: Option<Cow<'a, str>>,
140 #[doc = r"Restrict results to games released in the given year."]
141 released: Option<i64>,
142 #[doc = r"Restrict results to the given game type."]
143 gametype: Option<GameTypeId<'a>>,
144 #[doc = r"Restrict results to the given platform."]
145 platform: Option<PlatformId<'a>>,
146 #[doc = r"Restrict results to the given region."]
147 region: Option<RegionId<'a>>,
148 #[doc = r"Restrict results to the given genre."]
149 genre: Option<GenreId<'a>>,
150 #[doc = r"Restrict results to the given engine."]
151 engine: Option<EngineId<'a>>,
152 #[doc = r"Restrict results to the given developer."]
153 developer: Option<DeveloperId<'a>>,
154 #[doc = r"Restrict results to the given publisher."]
155 publisher: Option<PublisherId<'a>>,
156 #[doc = r"Only return games moderated by the given user."]
157 moderator: Option<UserId<'a>>,
158 #[doc = r"Enable bulk access."]
159 #[serde(rename = "_bulk")]
160 bulk: Option<bool>,
161 #[doc = r"Sorting options for results."]
162 orderby: Option<GamesSorting>,
163 #[doc = r"Sort direction."]
164 direction: Option<Direction>,
165 #[builder(setter(name = "_embed"), private)]
166 #[serde(serialize_with = "super::utils::serialize_as_csv")]
167 #[serde(skip_serializing_if = "BTreeSet::is_empty")]
168 embed: BTreeSet<GameEmbeds>,
169}
170
171#[derive(Debug, Builder, Clone)]
173#[builder(setter(into, strip_option))]
174pub struct Game<'a> {
175 #[doc = r"`ID` of the game."]
176 id: GameId<'a>,
177}
178
179#[derive(Debug, Builder, Serialize, Clone)]
181#[builder(setter(into, strip_option))]
182#[serde(rename_all = "kebab-case")]
183pub struct GameCategories<'a> {
184 #[doc = r"`ID` of the game to retrieve categories for."]
185 #[serde(skip)]
186 id: GameId<'a>,
187 #[doc = r"Filter miscellaneous categories."]
188 #[builder(default)]
189 miscellaneous: Option<bool>,
190 #[doc = r"Sorting options for results."]
191 #[builder(default)]
192 orderby: Option<CategoriesSorting>,
193 #[doc = r"Sort direction."]
194 #[builder(default)]
195 direction: Option<Direction>,
196 #[builder(setter(name = "_embed"), private, default)]
197 #[serde(serialize_with = "super::utils::serialize_as_csv")]
198 #[serde(skip_serializing_if = "BTreeSet::is_empty")]
199 embed: BTreeSet<CategoryEmbeds>,
200}
201
202impl GameCategoriesBuilder<'_> {
203 pub fn embed(&mut self, embed: CategoryEmbeds) -> &mut Self {
205 self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
206 self
207 }
208
209 pub fn embeds<I>(&mut self, iter: I) -> &mut Self
211 where
212 I: Iterator<Item = CategoryEmbeds>,
213 {
214 self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
215 self
216 }
217}
218
219#[derive(Debug, Builder, Serialize, Clone)]
221#[builder(setter(into, strip_option))]
222#[serde(rename_all = "kebab-case")]
223pub struct GameLevels<'a> {
224 #[doc = r"`ID` of the game to retrieve levels for."]
225 #[serde(skip)]
226 id: GameId<'a>,
227 #[doc = r"Sorting options for results."]
228 #[builder(default)]
229 orderby: Option<LevelsSorting>,
230 #[doc = r"Sort direction."]
231 #[builder(default)]
232 direction: Option<Direction>,
233}
234
235#[derive(Debug, Builder, Serialize, Clone)]
237#[builder(setter(into, strip_option))]
238#[serde(rename_all = "kebab-case")]
239pub struct GameVariables<'a> {
240 #[doc = r"`ID` of the game to retrieve variables for."]
241 #[serde(skip)]
242 id: GameId<'a>,
243 #[doc = r"Sorting options for results."]
244 #[builder(default)]
245 orderby: Option<VariablesSorting>,
246 #[doc = r"Sort direction."]
247 #[builder(default)]
248 direction: Option<Direction>,
249}
250
251#[derive(Default, Clone)]
253pub struct GameDerivedGamesBuilder<'a> {
254 id: Option<GameId<'a>>,
255 inner: GamesBuilder<'a>,
256}
257
258#[derive(Debug, Clone)]
260pub struct GameDerivedGames<'a> {
261 id: GameId<'a>,
262 inner: Games<'a>,
263}
264
265#[derive(Debug, Builder, Serialize, Clone)]
268#[builder(setter(into, strip_option))]
269#[serde(rename_all = "kebab-case")]
270pub struct GameRecords<'a> {
271 #[doc = r"`ID` of the game to retrieve records for."]
272 id: GameId<'a>,
273 #[doc = r"Return the `top` *places* (this can result in more than `top` runs!). Defaults to 3."]
274 #[builder(default)]
275 top: Option<i64>,
276 #[doc = r"When set to [`LeaderboardScope::FullGame`], only full-game categories will be included. When set to [`LeaderboardScope::Levels`] only individual levels are returned. Defaults to [`LeaderboardScope::All`]."]
277 #[builder(default)]
278 scope: Option<LeaderboardScope>,
279 #[doc = r"When `false`, miscellaneous categories will not be included in the results."]
280 #[builder(default)]
281 miscellaneous: Option<bool>,
282 #[doc = r"When `true`, empty leaderboards will not be included in the results."]
283 #[builder(default)]
284 skip_empty: Option<bool>,
285 #[builder(setter(name = "_embed"), private, default)]
286 #[serde(serialize_with = "super::utils::serialize_as_csv")]
287 #[serde(skip_serializing_if = "BTreeSet::is_empty")]
288 embed: BTreeSet<LeaderboardEmbeds>,
289}
290
291impl Games<'_> {
292 pub fn builder<'a>() -> GamesBuilder<'a> {
294 GamesBuilder::default()
295 }
296}
297
298impl GamesBuilder<'_> {
299 pub fn embed(&mut self, embed: GameEmbeds) -> &mut Self {
301 self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
302 self
303 }
304
305 pub fn embeds<I>(&mut self, iter: I) -> &mut Self
307 where
308 I: Iterator<Item = GameEmbeds>,
309 {
310 self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
311 self
312 }
313}
314
315impl Game<'_> {
316 pub fn builder<'a>() -> GameBuilder<'a> {
318 GameBuilder::default()
319 }
320}
321
322impl GameCategories<'_> {
323 pub fn builder<'a>() -> GameCategoriesBuilder<'a> {
325 GameCategoriesBuilder::default()
326 }
327}
328
329impl GameLevels<'_> {
330 pub fn builder<'a>() -> GameLevelsBuilder<'a> {
332 GameLevelsBuilder::default()
333 }
334}
335
336impl GameVariables<'_> {
337 pub fn builder<'a>() -> GameVariablesBuilder<'a> {
339 GameVariablesBuilder::default()
340 }
341}
342
343impl<'a> GameDerivedGamesBuilder<'a> {
344 pub fn id<S>(&mut self, value: S) -> &mut Self
346 where
347 S: Into<GameId<'a>>,
348 {
349 self.id = Some(value.into());
350 self
351 }
352
353 pub fn name<S>(&mut self, value: S) -> &mut Self
355 where
356 S: Into<Cow<'a, str>>,
357 {
358 self.inner.name(value);
359 self
360 }
361
362 pub fn abbreviation<S>(&mut self, value: S) -> &mut Self
364 where
365 S: Into<Cow<'a, str>>,
366 {
367 self.inner.abbreviation(value);
368 self
369 }
370
371 pub fn released<T>(&mut self, value: T) -> &mut Self
373 where
374 T: Into<i64>,
375 {
376 self.inner.released(value);
377 self
378 }
379
380 pub fn gametype<S>(&mut self, value: S) -> &mut Self
382 where
383 S: Into<GameTypeId<'a>>,
384 {
385 self.inner.gametype(value);
386 self
387 }
388
389 pub fn platform<S>(&mut self, value: S) -> &mut Self
391 where
392 S: Into<PlatformId<'a>>,
393 {
394 self.inner.platform(value);
395 self
396 }
397
398 pub fn region<S>(&mut self, value: S) -> &mut Self
400 where
401 S: Into<RegionId<'a>>,
402 {
403 self.inner.region(value);
404 self
405 }
406
407 pub fn genre<S>(&mut self, value: S) -> &mut Self
409 where
410 S: Into<GenreId<'a>>,
411 {
412 self.inner.genre(value);
413 self
414 }
415
416 pub fn engine<S>(&mut self, value: S) -> &mut Self
418 where
419 S: Into<EngineId<'a>>,
420 {
421 self.inner.engine(value);
422 self
423 }
424
425 pub fn developer<S>(&mut self, value: S) -> &mut Self
427 where
428 S: Into<DeveloperId<'a>>,
429 {
430 self.inner.developer(value);
431 self
432 }
433
434 pub fn publisher<S>(&mut self, value: S) -> &mut Self
436 where
437 S: Into<PublisherId<'a>>,
438 {
439 self.inner.publisher(value);
440 self
441 }
442
443 pub fn moderator<S>(&mut self, value: S) -> &mut Self
445 where
446 S: Into<UserId<'a>>,
447 {
448 self.inner.moderator(value);
449 self
450 }
451
452 pub fn bulk<T>(&mut self, value: T) -> &mut Self
454 where
455 T: Into<bool>,
456 {
457 self.inner.bulk(value);
458 self
459 }
460
461 pub fn orderby<V>(&mut self, value: V) -> &mut Self
463 where
464 V: Into<GamesSorting>,
465 {
466 self.inner.orderby(value);
467 self
468 }
469
470 pub fn direction<V>(&mut self, value: V) -> &mut Self
472 where
473 V: Into<Direction>,
474 {
475 self.inner.direction(value);
476 self
477 }
478
479 pub fn build(&self) -> Result<GameDerivedGames<'a>, GameDerivedGamesBuilderError> {
485 let inner = self.inner.build()?;
486 Ok(GameDerivedGames {
487 id: self
488 .id
489 .as_ref()
490 .cloned()
491 .ok_or(GameDerivedGamesBuilderError::UninitializedField("id"))?,
492 inner,
493 })
494 }
495}
496
497impl GameDerivedGames<'_> {
498 pub fn builder<'a>() -> GameDerivedGamesBuilder<'a> {
500 GameDerivedGamesBuilder::default()
501 }
502}
503
504impl GameRecords<'_> {
505 pub fn builder<'a>() -> GameRecordsBuilder<'a> {
507 GameRecordsBuilder::default()
508 }
509}
510
511impl GameRecordsBuilder<'_> {
512 pub fn embed(&mut self, embed: LeaderboardEmbeds) -> &mut Self {
514 self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
515 self
516 }
517
518 pub fn embeds<I>(&mut self, iter: I) -> &mut Self
520 where
521 I: Iterator<Item = LeaderboardEmbeds>,
522 {
523 self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
524 self
525 }
526}
527
528impl GameEmbeds {
529 fn as_str(&self) -> &'static str {
530 match self {
531 GameEmbeds::Levels => "levels",
532 GameEmbeds::Categories => "categories",
533 GameEmbeds::Moderators => "moderators",
534 GameEmbeds::Gametypes => "gametypes",
535 GameEmbeds::Platforms => "platforms",
536 GameEmbeds::Regions => "regions",
537 GameEmbeds::Genres => "genres",
538 GameEmbeds::Engines => "engines",
539 GameEmbeds::Developers => "developers",
540 GameEmbeds::Publishers => "publishers",
541 GameEmbeds::Variables => "variables",
542 }
543 }
544}
545
546impl Default for LevelsSorting {
547 fn default() -> Self {
548 Self::Pos
549 }
550}
551
552impl Endpoint for Games<'_> {
553 fn endpoint(&self) -> Cow<'static, str> {
554 "games".into()
555 }
556
557 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
558 QueryParams::with(self)
559 }
560}
561
562impl Endpoint for Game<'_> {
563 fn endpoint(&self) -> Cow<'static, str> {
564 format!("/games/{}", self.id).into()
565 }
566}
567
568impl Endpoint for GameCategories<'_> {
569 fn endpoint(&self) -> Cow<'static, str> {
570 format!("/games/{}/categories", self.id).into()
571 }
572
573 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
574 QueryParams::with(self)
575 }
576}
577
578impl Endpoint for GameLevels<'_> {
579 fn endpoint(&self) -> Cow<'static, str> {
580 format!("/games/{}/levels", self.id).into()
581 }
582
583 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
584 QueryParams::with(self)
585 }
586}
587
588impl Endpoint for GameVariables<'_> {
589 fn endpoint(&self) -> Cow<'static, str> {
590 format!("/games/{}/variables", self.id).into()
591 }
592
593 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
594 QueryParams::with(self)
595 }
596}
597
598impl Endpoint for GameDerivedGames<'_> {
599 fn endpoint(&self) -> Cow<'static, str> {
600 format!("/games/{}/derived-games", self.id).into()
601 }
602
603 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
604 QueryParams::with(&self.inner)
605 }
606}
607
608impl Endpoint for GameRecords<'_> {
609 fn endpoint(&self) -> Cow<'static, str> {
610 format!("/games/{}/records", self.id).into()
611 }
612
613 fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
614 QueryParams::with(self)
615 }
616}
617
618impl From<&GameEmbeds> for &'static str {
619 fn from(value: &GameEmbeds) -> Self {
620 value.as_str()
621 }
622}
623
624impl Pageable for GameDerivedGames<'_> {}
625
626impl Pageable for Games<'_> {}
627
628impl Pageable for GameRecords<'_> {}