speedrun_api/api/
users.rs1use 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#[derive(Debug, Serialize, Clone, Copy)]
15#[serde(rename_all = "kebab-case")]
16pub enum UsersSorting {
17 #[serde(rename = "name.int")]
19 NameInternational,
20 #[serde(rename = "name.jap")]
22 NameJapanese,
23 Signup,
25 Role,
27}
28
29#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
31pub struct UserId<'a>(Cow<'a, str>);
32
33impl<'a> UserId<'a> {
34 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#[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#[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#[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 pub fn builder<'a>() -> UsersBuilder<'a> {
115 UsersBuilder::default()
116 }
117}
118
119impl User<'_> {
120 pub fn builder<'a>() -> UserBuilder<'a> {
122 UserBuilder::default()
123 }
124}
125
126impl UserPersonalBests<'_> {
127 pub fn builder<'a>() -> UserPersonalBestsBuilder<'a> {
129 UserPersonalBestsBuilder::default()
130 }
131}
132
133impl UserPersonalBestsBuilder<'_> {
134 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 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<'_> {}