1use bon::bon;
13use futures::Stream;
14use serde::Deserialize;
15use url::Url;
16
17use crate::client::HFClient;
18use crate::error::HFResult;
19use crate::retry;
20
21#[derive(Debug, Clone, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct User {
30 #[serde(alias = "login", alias = "user", alias = "name")]
33 pub username: String,
34 pub fullname: Option<String>,
36 pub avatar_url: Option<String>,
38 #[serde(rename = "type")]
40 pub user_type: Option<String>,
41 pub details: Option<String>,
43 pub is_following: Option<bool>,
45 pub is_pro: Option<bool>,
47 pub num_models: Option<u64>,
49 pub num_datasets: Option<u64>,
51 pub num_spaces: Option<u64>,
53 pub num_discussions: Option<u64>,
55 pub num_papers: Option<u64>,
57 pub num_upvotes: Option<u64>,
59 pub num_likes: Option<u64>,
61 pub num_following: Option<u64>,
63 pub num_followers: Option<u64>,
65 pub email: Option<String>,
67 pub email_verified: Option<bool>,
69 pub plan: Option<String>,
71 pub can_pay: Option<bool>,
73 pub orgs: Option<Vec<OrgMembership>>,
76}
77
78#[derive(Debug, Clone, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct OrgMembership {
86 pub name: Option<String>,
88 pub fullname: Option<String>,
90 pub avatar_url: Option<String>,
92}
93
94#[derive(Debug, Clone, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct Organization {
100 pub name: String,
103 pub fullname: Option<String>,
105 pub avatar_url: Option<String>,
107 #[serde(rename = "type")]
109 pub org_type: Option<String>,
110 pub details: Option<String>,
112 pub is_verified: Option<bool>,
114 pub is_following: Option<bool>,
116 pub num_users: Option<u64>,
118 pub num_models: Option<u64>,
120 pub num_spaces: Option<u64>,
122 pub num_datasets: Option<u64>,
124 pub num_followers: Option<u64>,
126 pub num_papers: Option<u64>,
128 pub plan: Option<String>,
130}
131
132#[bon]
133impl HFClient {
134 #[builder(finish_fn = send, derive(Debug, Clone))]
142 pub async fn whoami(&self) -> HFResult<User> {
143 let url = format!("{}/api/whoami-v2", self.endpoint());
144 let headers = self.auth_headers();
145 let response =
146 retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
147 let response = self
148 .check_response(response, None, crate::error::NotFoundContext::Generic)
149 .await?;
150 Ok(response.json().await?)
151 }
152
153 #[builder(finish_fn = send, derive(Debug, Clone))]
161 pub async fn user_overview(
162 &self,
163 username: &str,
165 ) -> HFResult<User> {
166 let url = format!("{}/api/users/{}/overview", self.endpoint(), username);
167 let headers = self.auth_headers();
168 let response =
169 retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
170 let response = self
171 .check_response(response, None, crate::error::NotFoundContext::Generic)
172 .await?;
173 Ok(response.json().await?)
174 }
175
176 #[builder(finish_fn = send, derive(Debug, Clone))]
184 pub async fn organization_overview(
185 &self,
186 organization: &str,
188 ) -> HFResult<Organization> {
189 let url = format!("{}/api/organizations/{}/overview", self.endpoint(), organization);
190 let headers = self.auth_headers();
191 let response =
192 retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
193 let response = self
194 .check_response(response, None, crate::error::NotFoundContext::Generic)
195 .await?;
196 Ok(response.json().await?)
197 }
198
199 #[builder(finish_fn = send, derive(Debug, Clone))]
208 pub fn list_user_followers(
209 &self,
210 #[builder(into)]
212 username: String,
213 limit: Option<usize>,
215 ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
216 let url = Url::parse(&format!("{}/api/users/{}/followers", self.endpoint(), username))?;
217 Ok(self.paginate(url, vec![], limit))
218 }
219
220 #[builder(finish_fn = send, derive(Debug, Clone))]
229 pub fn list_user_following(
230 &self,
231 #[builder(into)]
233 username: String,
234 limit: Option<usize>,
236 ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
237 let url = Url::parse(&format!("{}/api/users/{}/following", self.endpoint(), username))?;
238 Ok(self.paginate(url, vec![], limit))
239 }
240
241 #[builder(finish_fn = send, derive(Debug, Clone))]
250 pub fn list_organization_members(
251 &self,
252 #[builder(into)]
254 organization: String,
255 limit: Option<usize>,
257 ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
258 let url = Url::parse(&format!("{}/api/organizations/{}/members", self.endpoint(), organization))?;
259 Ok(self.paginate(url, vec![], limit))
260 }
261}
262
263#[cfg(feature = "blocking")]
264#[bon]
265impl crate::blocking::HFClientSync {
266 #[builder(finish_fn = send, derive(Debug, Clone))]
268 pub fn whoami(&self) -> HFResult<User> {
269 self.runtime.block_on(self.inner.whoami().send())
270 }
271
272 #[builder(finish_fn = send, derive(Debug, Clone))]
275 pub fn user_overview(&self, username: &str) -> HFResult<User> {
276 self.runtime.block_on(self.inner.user_overview().username(username).send())
277 }
278
279 #[builder(finish_fn = send, derive(Debug, Clone))]
282 pub fn organization_overview(&self, organization: &str) -> HFResult<Organization> {
283 self.runtime
284 .block_on(self.inner.organization_overview().organization(organization).send())
285 }
286
287 #[builder(finish_fn = send, derive(Debug, Clone))]
290 pub fn list_user_followers(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
291 use futures::StreamExt;
292 self.runtime.block_on(async move {
293 let stream = self.inner.list_user_followers().username(username).maybe_limit(limit).send()?;
294 futures::pin_mut!(stream);
295 let mut items = Vec::new();
296 while let Some(item) = stream.next().await {
297 items.push(item?);
298 }
299 Ok(items)
300 })
301 }
302
303 #[builder(finish_fn = send, derive(Debug, Clone))]
306 pub fn list_user_following(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
307 use futures::StreamExt;
308 self.runtime.block_on(async move {
309 let stream = self.inner.list_user_following().username(username).maybe_limit(limit).send()?;
310 futures::pin_mut!(stream);
311 let mut items = Vec::new();
312 while let Some(item) = stream.next().await {
313 items.push(item?);
314 }
315 Ok(items)
316 })
317 }
318
319 #[builder(finish_fn = send, derive(Debug, Clone))]
322 pub fn list_organization_members(
323 &self,
324 #[builder(into)] organization: String,
325 limit: Option<usize>,
326 ) -> HFResult<Vec<User>> {
327 use futures::StreamExt;
328 self.runtime.block_on(async move {
329 let stream = self
330 .inner
331 .list_organization_members()
332 .organization(organization)
333 .maybe_limit(limit)
334 .send()?;
335 futures::pin_mut!(stream);
336 let mut items = Vec::new();
337 while let Some(item) = stream.next().await {
338 items.push(item?);
339 }
340 Ok(items)
341 })
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::{Organization, User};
348
349 #[test]
350 fn test_user_full_profile() {
351 let json = r#"{
352 "user":"alice",
353 "fullname":"Alice Anderson",
354 "avatarUrl":"https://example/a.png",
355 "type":"user",
356 "details":"researcher",
357 "isFollowing":true,
358 "isPro":false,
359 "numModels":12,
360 "numDatasets":3,
361 "numSpaces":1,
362 "numDiscussions":4,
363 "numPapers":2,
364 "numUpvotes":5,
365 "numLikes":6,
366 "numFollowing":7,
367 "numFollowers":8
368 }"#;
369 let user: User = serde_json::from_str(json).unwrap();
370 assert_eq!(user.username, "alice");
371 assert_eq!(user.details.as_deref(), Some("researcher"));
372 assert_eq!(user.is_following, Some(true));
373 assert_eq!(user.num_models, Some(12));
374 assert_eq!(user.num_datasets, Some(3));
375 assert_eq!(user.num_spaces, Some(1));
376 assert_eq!(user.num_discussions, Some(4));
377 assert_eq!(user.num_papers, Some(2));
378 assert_eq!(user.num_upvotes, Some(5));
379 assert_eq!(user.num_likes, Some(6));
380 assert_eq!(user.num_following, Some(7));
381 assert_eq!(user.num_followers, Some(8));
382 }
383
384 #[test]
385 fn test_organization_full_profile() {
386 let json = r#"{
387 "name":"acme",
388 "fullname":"Acme Corp",
389 "avatarUrl":"https://example/o.png",
390 "type":"org",
391 "details":"description",
392 "isVerified":true,
393 "isFollowing":false,
394 "numUsers":42,
395 "numModels":7,
396 "numSpaces":2,
397 "numDatasets":3,
398 "numFollowers":100,
399 "numPapers":5,
400 "plan":"enterprise"
401 }"#;
402 let org: Organization = serde_json::from_str(json).unwrap();
403 assert_eq!(org.name, "acme");
404 assert_eq!(org.details.as_deref(), Some("description"));
405 assert_eq!(org.is_verified, Some(true));
406 assert_eq!(org.is_following, Some(false));
407 assert_eq!(org.num_users, Some(42));
408 assert_eq!(org.num_models, Some(7));
409 assert_eq!(org.num_spaces, Some(2));
410 assert_eq!(org.num_datasets, Some(3));
411 assert_eq!(org.num_followers, Some(100));
412 assert_eq!(org.num_papers, Some(5));
413 assert_eq!(org.plan.as_deref(), Some("enterprise"));
414 }
415}