speedrun_api/api/
users.rs

1//! # Users
2//!
3//! Endpoints available for users
4use std::{borrow::Cow, collections::BTreeSet, fmt::Display};
5
6use serde::{Deserialize, Serialize};
7
8use super::{
9    endpoint::Endpoint, error::BodyError, games::GameId, query_params::QueryParams,
10    runs::RunEmbeds, Direction, Pageable,
11};
12
13/// Sorting options for users
14#[derive(Debug, Serialize, Clone, Copy)]
15#[serde(rename_all = "kebab-case")]
16pub enum UsersSorting {
17    /// Sorts alphanumerically by the international name (default)
18    #[serde(rename = "name.int")]
19    NameInternational,
20    /// Sorts alphanumerically by the Japanese name
21    #[serde(rename = "name.jap")]
22    NameJapanese,
23    /// Sorts by the signup date
24    Signup,
25    /// Sorts by the user role
26    Role,
27}
28
29/// Represents a user ID
30#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
31pub struct UserId<'a>(Cow<'a, str>);
32
33impl<'a> UserId<'a> {
34    /// Create a new user ID
35    pub fn new<T>(id: T) -> Self
36    where
37        T: Into<Cow<'a, str>>,
38    {
39        Self(id.into())
40    }
41}
42
43impl<'a, T> From<T> for UserId<'a>
44where
45    T: Into<Cow<'a, str>>,
46{
47    fn from(value: T) -> Self {
48        Self::new(value)
49    }
50}
51
52impl Display for UserId<'_> {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(f, "{}", &self.0)
55    }
56}
57
58/// Retrieves a list of all users
59#[derive(Default, Debug, Builder, Serialize, Clone)]
60#[builder(default, setter(into, strip_option))]
61#[serde(rename_all = "kebab-case")]
62pub struct Users<'a> {
63    #[doc = r"Performs a case-insensitive exact-match search for `lookup` across all user names, URLs and social profiles. Cannot be specified with any other filters."]
64    lookup: Option<Cow<'a, str>>,
65    #[doc = r"Only return users whose name/URL contain `name`. The comparison is case-insensitive."]
66    name: Option<Cow<'a, str>>,
67    #[doc = r"Search Twitch usernames"]
68    twitch: Option<Cow<'a, str>>,
69    #[doc = r"Search Hitbox usernames"]
70    hitbox: Option<Cow<'a, str>>,
71    #[doc = r"Search Twitter usernames"]
72    twitter: Option<Cow<'a, str>>,
73    #[doc = r"Search SpeedRunsLive usernames"]
74    speedrunslive: Option<Cow<'a, str>>,
75    #[doc = r"Sorting options for results."]
76    orderby: Option<UsersSorting>,
77    #[doc = r"Sort direction."]
78    direction: Option<Direction>,
79}
80
81/// Retrieves a single user
82#[derive(Debug, Builder, Clone)]
83#[builder(setter(into, strip_option))]
84pub struct User<'a> {
85    #[doc = r"User ID or username. Using an ID is recommended over a username, because usernames can change."]
86    id: UserId<'a>,
87}
88
89/// Retrieves a list of runs representing the personal bests for a user
90#[derive(Debug, Builder, Serialize, Clone)]
91#[builder(setter(into, strip_option))]
92#[serde(rename_all = "kebab-case")]
93pub struct UserPersonalBests<'a> {
94    #[doc = r"User ID or username. Using an ID is recommended over a username, because usernames can change."]
95    #[serde(skip)]
96    id: UserId<'a>,
97    #[doc = r"Only return PBs with a place equal or better than `top` (e.g. a value of `1` will return all world records for the given user)"]
98    #[builder(default)]
99    top: Option<i64>,
100    #[doc = r"Series ID or abbreviation. When given, restricts results to that games and romhacks in that series."]
101    #[builder(default)]
102    series: Option<Cow<'a, str>>,
103    #[doc = r"Game ID or abbreviation. When given, restricts results to that game"]
104    #[builder(default)]
105    game: Option<GameId<'a>>,
106    #[builder(setter(name = "_embed"), private, default)]
107    #[serde(serialize_with = "super::utils::serialize_as_csv")]
108    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
109    embed: BTreeSet<RunEmbeds>,
110}
111
112impl Users<'_> {
113    /// Create a builder for this endpoint.
114    pub fn builder<'a>() -> UsersBuilder<'a> {
115        UsersBuilder::default()
116    }
117}
118
119impl User<'_> {
120    /// Create a builder for this endpoint.
121    pub fn builder<'a>() -> UserBuilder<'a> {
122        UserBuilder::default()
123    }
124}
125
126impl UserPersonalBests<'_> {
127    /// Create a builder for this endpoint.
128    pub fn builder<'a>() -> UserPersonalBestsBuilder<'a> {
129        UserPersonalBestsBuilder::default()
130    }
131}
132
133impl UserPersonalBestsBuilder<'_> {
134    /// Add an embedded resource to this result.
135    pub fn embed(&mut self, embed: RunEmbeds) -> &mut Self {
136        self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
137        self
138    }
139
140    /// Add multiple embedded resources to this result.
141    pub fn embeds<I>(&mut self, iter: I) -> &mut Self
142    where
143        I: Iterator<Item = RunEmbeds>,
144    {
145        self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
146        self
147    }
148}
149
150impl Default for UsersSorting {
151    fn default() -> Self {
152        Self::NameInternational
153    }
154}
155
156impl Endpoint for Users<'_> {
157    fn endpoint(&self) -> Cow<'static, str> {
158        "/users".into()
159    }
160
161    fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
162        QueryParams::with(self)
163    }
164}
165
166impl Endpoint for User<'_> {
167    fn endpoint(&self) -> Cow<'static, str> {
168        format!("/users/{}", self.id).into()
169    }
170}
171
172impl Endpoint for UserPersonalBests<'_> {
173    fn endpoint(&self) -> Cow<'static, str> {
174        format!("/users/{}/personal-bests", self.id).into()
175    }
176
177    fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
178        QueryParams::with(self)
179    }
180}
181
182impl Pageable for Users<'_> {}