use bon::bon;
use futures::Stream;
use serde::Deserialize;
use url::Url;
use crate::client::HFClient;
use crate::error::HFResult;
use crate::retry;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
#[serde(alias = "login", alias = "user", alias = "name")]
pub username: String,
pub fullname: Option<String>,
pub avatar_url: Option<String>,
#[serde(rename = "type")]
pub user_type: Option<String>,
pub details: Option<String>,
pub is_following: Option<bool>,
pub is_pro: Option<bool>,
pub num_models: Option<u64>,
pub num_datasets: Option<u64>,
pub num_spaces: Option<u64>,
pub num_discussions: Option<u64>,
pub num_papers: Option<u64>,
pub num_upvotes: Option<u64>,
pub num_likes: Option<u64>,
pub num_following: Option<u64>,
pub num_followers: Option<u64>,
pub email: Option<String>,
pub email_verified: Option<bool>,
pub plan: Option<String>,
pub can_pay: Option<bool>,
pub orgs: Option<Vec<OrgMembership>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrgMembership {
pub name: Option<String>,
pub fullname: Option<String>,
pub avatar_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Organization {
pub name: String,
pub fullname: Option<String>,
pub avatar_url: Option<String>,
#[serde(rename = "type")]
pub org_type: Option<String>,
pub details: Option<String>,
pub is_verified: Option<bool>,
pub is_following: Option<bool>,
pub num_users: Option<u64>,
pub num_models: Option<u64>,
pub num_spaces: Option<u64>,
pub num_datasets: Option<u64>,
pub num_followers: Option<u64>,
pub num_papers: Option<u64>,
pub plan: Option<String>,
}
#[bon]
impl HFClient {
#[builder(finish_fn = send, derive(Debug, Clone))]
pub async fn whoami(&self) -> HFResult<User> {
let url = format!("{}/api/whoami-v2", self.endpoint());
let headers = self.auth_headers();
let response =
retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
let response = self
.check_response(response, None, crate::error::NotFoundContext::Generic)
.await?;
Ok(response.json().await?)
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub async fn user_overview(
&self,
username: &str,
) -> HFResult<User> {
let url = format!("{}/api/users/{}/overview", self.endpoint(), username);
let headers = self.auth_headers();
let response =
retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
let response = self
.check_response(response, None, crate::error::NotFoundContext::Generic)
.await?;
Ok(response.json().await?)
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub async fn organization_overview(
&self,
organization: &str,
) -> HFResult<Organization> {
let url = format!("{}/api/organizations/{}/overview", self.endpoint(), organization);
let headers = self.auth_headers();
let response =
retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
let response = self
.check_response(response, None, crate::error::NotFoundContext::Generic)
.await?;
Ok(response.json().await?)
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_user_followers(
&self,
#[builder(into)]
username: String,
limit: Option<usize>,
) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
let url = Url::parse(&format!("{}/api/users/{}/followers", self.endpoint(), username))?;
Ok(self.paginate(url, vec![], limit))
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_user_following(
&self,
#[builder(into)]
username: String,
limit: Option<usize>,
) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
let url = Url::parse(&format!("{}/api/users/{}/following", self.endpoint(), username))?;
Ok(self.paginate(url, vec![], limit))
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_organization_members(
&self,
#[builder(into)]
organization: String,
limit: Option<usize>,
) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
let url = Url::parse(&format!("{}/api/organizations/{}/members", self.endpoint(), organization))?;
Ok(self.paginate(url, vec![], limit))
}
}
#[cfg(feature = "blocking")]
#[bon]
impl crate::blocking::HFClientSync {
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn whoami(&self) -> HFResult<User> {
self.runtime.block_on(self.inner.whoami().send())
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn user_overview(&self, username: &str) -> HFResult<User> {
self.runtime.block_on(self.inner.user_overview().username(username).send())
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn organization_overview(&self, organization: &str) -> HFResult<Organization> {
self.runtime
.block_on(self.inner.organization_overview().organization(organization).send())
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_user_followers(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
use futures::StreamExt;
self.runtime.block_on(async move {
let stream = self.inner.list_user_followers().username(username).maybe_limit(limit).send()?;
futures::pin_mut!(stream);
let mut items = Vec::new();
while let Some(item) = stream.next().await {
items.push(item?);
}
Ok(items)
})
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_user_following(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
use futures::StreamExt;
self.runtime.block_on(async move {
let stream = self.inner.list_user_following().username(username).maybe_limit(limit).send()?;
futures::pin_mut!(stream);
let mut items = Vec::new();
while let Some(item) = stream.next().await {
items.push(item?);
}
Ok(items)
})
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_organization_members(
&self,
#[builder(into)] organization: String,
limit: Option<usize>,
) -> HFResult<Vec<User>> {
use futures::StreamExt;
self.runtime.block_on(async move {
let stream = self
.inner
.list_organization_members()
.organization(organization)
.maybe_limit(limit)
.send()?;
futures::pin_mut!(stream);
let mut items = Vec::new();
while let Some(item) = stream.next().await {
items.push(item?);
}
Ok(items)
})
}
}
#[cfg(test)]
mod tests {
use super::{Organization, User};
#[test]
fn test_user_full_profile() {
let json = r#"{
"user":"alice",
"fullname":"Alice Anderson",
"avatarUrl":"https://example/a.png",
"type":"user",
"details":"researcher",
"isFollowing":true,
"isPro":false,
"numModels":12,
"numDatasets":3,
"numSpaces":1,
"numDiscussions":4,
"numPapers":2,
"numUpvotes":5,
"numLikes":6,
"numFollowing":7,
"numFollowers":8
}"#;
let user: User = serde_json::from_str(json).unwrap();
assert_eq!(user.username, "alice");
assert_eq!(user.details.as_deref(), Some("researcher"));
assert_eq!(user.is_following, Some(true));
assert_eq!(user.num_models, Some(12));
assert_eq!(user.num_datasets, Some(3));
assert_eq!(user.num_spaces, Some(1));
assert_eq!(user.num_discussions, Some(4));
assert_eq!(user.num_papers, Some(2));
assert_eq!(user.num_upvotes, Some(5));
assert_eq!(user.num_likes, Some(6));
assert_eq!(user.num_following, Some(7));
assert_eq!(user.num_followers, Some(8));
}
#[test]
fn test_organization_full_profile() {
let json = r#"{
"name":"acme",
"fullname":"Acme Corp",
"avatarUrl":"https://example/o.png",
"type":"org",
"details":"description",
"isVerified":true,
"isFollowing":false,
"numUsers":42,
"numModels":7,
"numSpaces":2,
"numDatasets":3,
"numFollowers":100,
"numPapers":5,
"plan":"enterprise"
}"#;
let org: Organization = serde_json::from_str(json).unwrap();
assert_eq!(org.name, "acme");
assert_eq!(org.details.as_deref(), Some("description"));
assert_eq!(org.is_verified, Some(true));
assert_eq!(org.is_following, Some(false));
assert_eq!(org.num_users, Some(42));
assert_eq!(org.num_models, Some(7));
assert_eq!(org.num_spaces, Some(2));
assert_eq!(org.num_datasets, Some(3));
assert_eq!(org.num_followers, Some(100));
assert_eq!(org.num_papers, Some(5));
assert_eq!(org.plan.as_deref(), Some("enterprise"));
}
}