Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gists API: Complete support #371

Merged
merged 6 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/api/gists.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
//! The gist API
//!
//! Supports CRUD operations on gists in GitHub.
//!
//! [Official documentation][docs]
//!
//! [docs]: https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28
mod list_commits;
mod list_forks;
mod list_gists;

use http::StatusCode;
use serde::Serialize;
use std::collections::BTreeMap;

pub use self::list_commits::ListCommitsBuilder;
pub use self::list_gists::{ListAllGistsBuilder, ListPublicGistsBuilder, ListUserGistsBuilder};

use crate::{
models::gists::{Gist, GistRevision},
Octocrab, Result,
Expand All @@ -24,6 +33,100 @@ impl<'octo> GistsHandler<'octo> {
Self { crab }
}

/// List all gists from GitHub's gist API.
///
/// See: [GitHub API Documentation][docs] for `GET /gists`
///
/// # Note
/// * Calling with an authentication token will list all the gists of the
/// authenticated user
///
/// * If no authentication token will list all the public gists from
/// GitHub's API. This can potentially produce a lot of results, so care is
/// advised.
///
/// # Example
///
/// 1) This shows one page of (10) results for all public gists created a
/// from the day before:
///
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// let yesterday: chrono::DateTime<chrono::Utc> =
/// chrono::Utc::now()
/// .checked_sub_days(chrono::Days::new(1)).unwrap();
/// octocrab::instance()
/// .gists()
/// .list_all_gists()
/// .since(yesterday)
/// .page(1u32)
/// .per_page(10u8)
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
///
/// [docs]: https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28#list-gists-for-the-authenticated-user
pub fn list_all_gists(&self) -> ListAllGistsBuilder<'octo> {
ListAllGistsBuilder::new(self.crab)
}

/// List public gists sorted by most recently updated to least recently
/// updated. This works similarly to the `GistsHandler::list_all_gists`
///
/// See: [GitHub API Documentation][docs] for `GET /gists/public`
///
/// # Example
///
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// let yesterday: chrono::DateTime<chrono::Utc> =
/// chrono::Utc::now()
/// .checked_sub_days(chrono::Days::new(1)).unwrap();
/// let all_public_gists = octocrab::instance()
/// .gists()
/// .list_all_recent_public_gists()
/// .since(yesterday)
/// .page(1u32)
/// .per_page(10u8)
/// .send()
/// .await?;
/// # Ok(())
/// # }
///
/// ```
///
/// [docs]: https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28#list-public-gists
pub fn list_all_recent_public_gists(&self) -> ListPublicGistsBuilder<'octo> {
ListPublicGistsBuilder::new(self.crab)
}

/// List gists for the given username, allowing for pagination.
///
/// See [GitHub API Documentation][docs] for details on `GET /users/{username}/gists`
///
/// # Examples
///
/// * Fetch 10 recent gists for the user with login "foouser":
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// octocrab::instance()
/// .gists()
/// .list_user_gists("foouser")
/// .page(1u32)
/// .per_page(10u8)
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
///
/// [docs]: https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28#list-gists-for-a-user
pub fn list_user_gists(&self, username: impl AsRef<str>) -> ListUserGistsBuilder<'octo> {
ListUserGistsBuilder::new(self.crab, username.as_ref().to_string())
}

/// Create a new gist.
///
/// ```no_run
Expand Down
141 changes: 141 additions & 0 deletions src/api/gists/list_gists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::marker::PhantomData;

use super::*;
use chrono::{DateTime, Utc};

pub trait EndpointSelector {
const ENDPOINT: &'static str;
}
pub struct AllOrByAuth;
pub struct PublicOnly;

impl EndpointSelector for AllOrByAuth {
const ENDPOINT: &'static str = "/gists";
}

impl EndpointSelector for PublicOnly {
const ENDPOINT: &'static str = "/gists/public";
}

#[derive(Debug, serde::Serialize)]
pub struct ListGistsBuilder<'octo, T: EndpointSelector> {
#[serde(skip)]
visibility_type: PhantomData<T>,

#[serde(skip)]
crab: &'octo Octocrab,

/// Only show gists that were created after this UTC timestamp.
#[serde(skip_serializing_if = "Option::is_none")]
since: Option<DateTime<Utc>>,

/// The maximum number of results in each page retrieved.
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,

/// The page number to fetch. This starts at (and defaults to) 1
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl<'octo, T: EndpointSelector> ListGistsBuilder<'octo, T> {
pub fn new(crab: &'octo Octocrab) -> Self {
Self {
visibility_type: PhantomData,
crab,
since: None,
per_page: None,
page: None,
}
}

/// Only show gists that were created after this UTC timestamp.
pub fn since(mut self, created_after: impl Into<DateTime<Utc>>) -> Self {
self.since = Some(created_after.into());
self
}

/// The maximum number of results in each page retrieved.
pub fn per_page(mut self, count: u8) -> Self {
self.per_page = Some(count);
self
}

/// The page number to fetch. This starts at (and defaults to) 1
pub fn page(mut self, number: u32) -> Self {
self.page = Some(number);
self
}

/// Sends the actual request.
pub async fn send(self) -> crate::Result<crate::Page<crate::models::gists::Gist>> {
self.crab.get(T::ENDPOINT, Some(&self)).await
}
}

/// Handles query data for the `GET /gists` endpoint.
///
/// This endpoint has differing behaviour depending on the status of
/// authentication.
pub type ListAllGistsBuilder<'octo> = ListGistsBuilder<'octo, AllOrByAuth>;

/// Handles query data for the `GET /gists/public` endpoint.
///
/// Fetches all publicly available gists on the GitHub instance with pagination.
pub type ListPublicGistsBuilder<'octo> = ListGistsBuilder<'octo, PublicOnly>;

/// Handles query data for the `GET /users/{username}/gists` endpoint.
#[derive(Debug, serde::Serialize)]
pub struct ListUserGistsBuilder<'octo> {
#[serde(skip)]
crab: &'octo Octocrab,

#[serde(skip)]
/// Username for which to retrieve gists
username: String,

#[serde(skip_serializing_if = "Option::is_none")]
since: Option<DateTime<Utc>>,

#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,

#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl<'octo> ListUserGistsBuilder<'octo> {
pub fn new(crab: &'octo Octocrab, username: String) -> Self {
Self {
crab,
username,
since: None,
per_page: None,
page: None,
}
}

pub fn since(mut self, last_updated: DateTime<Utc>) -> Self {
self.since = Some(last_updated);
self
}

pub fn per_page(mut self, count: u8) -> Self {
self.per_page = Some(count);
self
}

pub fn page(mut self, number: u32) -> Self {
self.page = Some(number);
self
}

pub async fn send(self) -> crate::Result<crate::Page<Gist>> {
self.crab
.get(
format!("/users/{username}/gists", username = self.username),
Some(&self),
)
.await
}
}