Skip to content

Commit

Permalink
Added basic support for getting user profiles (#616)
Browse files Browse the repository at this point in the history
This also adds support for listing who a user follows and who is
following them.
  • Loading branch information
mcarson-sandia authored Mar 27, 2024
1 parent 6004d50 commit d3c70a1
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/api/users.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! The users API.
mod follow;
mod user_repos;

use self::follow::{ListUserFollowerBuilder, ListUserFollowingBuilder};
pub use self::user_repos::ListUserReposBuilder;
use crate::Octocrab;

Expand All @@ -15,6 +17,24 @@ impl<'octo> UserHandler<'octo> {
Self { crab, user }
}

/// Get this users profile info
pub async fn profile(&self) -> crate::Result<crate::models::UserProfile> {
// build the route to get info on this user
let route = format!("/users/{}", self.user);
// get info on the specified user
self.crab.get(route, None::<&()>).await
}

/// List this users that follow this user
pub fn followers(&self) -> ListUserFollowerBuilder {
ListUserFollowerBuilder::new(self)
}

/// List this user is following
pub fn following(&self) -> ListUserFollowingBuilder {
ListUserFollowingBuilder::new(self)
}

pub fn repos(&self) -> ListUserReposBuilder<'_, '_> {
ListUserReposBuilder::new(self)
}
Expand Down
86 changes: 86 additions & 0 deletions src/api/users/follow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::api::users::UserHandler;
use crate::Page;

/// A builder pattern struct for listing a users followers
///
/// created by [`UserHandler::followers`]
#[derive(serde::Serialize)]
pub struct ListUserFollowerBuilder<'octo, 'r> {
#[serde(skip)]
handler: &'r UserHandler<'octo>,
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl<'octo, 'b> ListUserFollowerBuilder<'octo, 'b> {
pub(crate) fn new(handler: &'b UserHandler<'octo>) -> Self {
Self {
handler,
per_page: None,
page: None,
}
}

/// Results per page (max 100).
pub fn per_page(mut self, per_page: impl Into<u8>) -> Self {
self.per_page = Some(per_page.into());
self
}

/// Page number of the results to fetch.
pub fn page(mut self, page: impl Into<u32>) -> Self {
self.page = Some(page.into());
self
}

/// Sends the actual request.
pub async fn send(self) -> crate::Result<Page<crate::models::Follower>> {
// build the route to get this users followers
let route = format!("/users/{}/followers", self.handler.user);
self.handler.crab.get(route, Some(&self)).await
}
}

/// A builder pattern struct for listing who a user is following
///
/// created by [`UserHandler::following`]
#[derive(serde::Serialize)]
pub struct ListUserFollowingBuilder<'octo, 'r> {
#[serde(skip)]
handler: &'r UserHandler<'octo>,
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl<'octo, 'b> ListUserFollowingBuilder<'octo, 'b> {
pub(crate) fn new(handler: &'b UserHandler<'octo>) -> Self {
Self {
handler,
per_page: None,
page: None,
}
}

/// Results per page (max 100).
pub fn per_page(mut self, per_page: impl Into<u8>) -> Self {
self.per_page = Some(per_page.into());
self
}

/// Page number of the results to fetch.
pub fn page(mut self, page: impl Into<u32>) -> Self {
self.page = Some(page.into());
self
}

/// Sends the actual request.
pub async fn send(self) -> crate::Result<Page<crate::models::Followee>> {
// build the route to get this users followers
let route = format!("/users/{}/following", self.handler.user);
self.handler.crab.get(route, Some(&self)).await
}
}
105 changes: 105 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,111 @@ pub struct Author {
pub email: Option<String>,
}

/// If a string is empty then deserialize it as none
fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
// try to deserialize our input string
let cast = String::deserialize(deserializer)?;
// if this string is empty then return None
if cast.is_empty() {
Ok(None)
} else {
Ok(Some(cast))
}
}

/// The full profile for a user
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct UserProfile {
pub login: String,
pub id: UserId,
pub node_id: String,
pub avatar_url: Url,
pub gravatar_id: String,
pub url: Url,
pub html_url: Url,
pub followers_url: Url,
pub following_url: Url,
pub gists_url: Url,
pub starred_url: Url,
pub subscriptions_url: Url,
pub organizations_url: Url,
pub repos_url: Url,
pub events_url: Url,
pub received_events_url: Url,
pub r#type: String,
pub site_admin: bool,
pub name: String,
pub company: Option<String>,
#[serde(deserialize_with = "empty_string_is_none")]
pub blog: Option<String>,
pub location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
pub hireable: Option<bool>,
pub bio: Option<String>,
pub twitter_username: Option<String>,
pub public_repos: u64,
pub public_gists: u64,
pub followers: u64,
pub following: u64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

/// A user that is following another user
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Follower {
pub login: String,
pub id: UserId,
pub node_id: String,
pub avatar_url: Url,
pub gravatar_id: String,
pub url: Url,
pub html_url: Url,
pub followers_url: Url,
pub following_url: Url,
pub gists_url: Url,
pub starred_url: Url,
pub subscriptions_url: Url,
pub organizations_url: Url,
pub repos_url: Url,
pub events_url: Url,
pub received_events_url: Url,
pub r#type: String,
pub site_admin: bool,
pub patch_url: Option<String>,
}

/// A user that is being followed by another user
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Followee {
pub login: String,
pub id: UserId,
pub node_id: String,
pub avatar_url: Url,
pub gravatar_id: String,
pub url: Url,
pub html_url: Url,
pub followers_url: Url,
pub following_url: Url,
pub gists_url: Url,
pub starred_url: Url,
pub subscriptions_url: Url,
pub organizations_url: Url,
pub repos_url: Url,
pub events_url: Url,
pub received_events_url: Url,
pub r#type: String,
pub site_admin: bool,
pub patch_url: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[non_exhaustive]
Expand Down

0 comments on commit d3c70a1

Please sign in to comment.