wakapi 0.3.1

Wakatime API client
Documentation
//! Commit endpoint
//!
//! - Ref : <https://wakatime.com/developers#commit>
//! - Last checked : 2026-05-15
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::{client::WakapiClient, error::WakapiError};

/// Request parameters for the Commit endpoint.
///
/// Ref : <https://wakatime.com/developers#commit>
pub struct CommitParams {
    url: CommitParamsUrl,
    params: CommitParamsParams,
}

struct CommitParamsUrl {
    /// required - Commit hash
    hash: String,
    /// required - Project
    project: String,
}

#[derive(Serialize, Default)]
struct CommitParamsParams {
    /// optional - Filter the commit to a branch; defaults to the repo’s default branch name
    branch: Option<String>,
}

impl CommitParams {
    /// Create a new CommitParams with default values (no branch).
    /// The project and hash parameters are required.
    pub fn new(project: &str, hash: &str) -> CommitParams {
        CommitParams {
            url: CommitParamsUrl {
                hash: hash.to_string(),
                project: project.to_string(),
            },
            params: CommitParamsParams { branch: None },
        }
    }

    /// Set the branch parameter for the request.
    pub fn branch(mut self, branch: &str) -> CommitParams {
        self.params.branch = Some(branch.to_string());
        self
    }

    /// Clear the branch parameter for the request.
    pub fn no_branch(mut self) -> CommitParams {
        self.params.branch = None;
        self
    }
}

/// Commit endpoint
///
/// Ref : <https://wakatime.com/developers#commit>
#[derive(Deserialize, Debug)]
pub struct Commit {
    /// Branch name containing the commit
    pub branch: String,
    /// Details of the commit
    pub commit: CommitDetails,
    /// Project details
    pub project: CommitProjectDetails,
    /// Project's sync status
    pub status: String,
}

/// Details of the commit
#[derive(Deserialize, Debug)]
pub struct CommitDetails {
    /// URL of author's avatar image
    pub author_avatar_url: Option<String>,
    /// Time when commit was authored in ISO 8601 format
    pub author_date: Option<DateTime<Utc>>,
    /// Email address of author
    pub author_email: Option<String>,
    /// Link to author's profile on GitHub, Bitbucket, GitLab, etc.
    pub author_html_url: Option<String>,
    /// Name of author
    pub author_name: Option<String>,
    /// API URL for author's profile
    pub author_url: Option<String>,
    /// Author's username
    pub author_username: Option<String>,
    /// Branch name, e.g., master
    pub branch: String,
    /// URL of committer's avatar image
    pub committer_avatar_url: Option<String>,
    /// Commit time in ISO 8601 format
    pub committer_date: Option<DateTime<Utc>>,
    /// Email address of committer
    pub committer_email: Option<String>,
    /// Link to committer's profile on GitHub, Bitbucket, GitLab, etc.
    pub committer_html_url: Option<String>,
    /// Name of committer
    pub committer_name: Option<String>,
    /// API URL for committer's profile
    pub committer_url: Option<String>,
    /// Committer's username
    pub committer_username: Option<String>,
    /// Time commit was synced in ISO 8601 format
    pub created_at: Option<DateTime<Utc>>,
    /// Revision control hash of this commit
    pub hash: String,
    /// Link to an HTML page with details about the current commit
    pub html_url: String,
    /// Time coded in editor for this commit
    pub human_readable_total: String,
    /// Time coded in editor for this commit with seconds
    pub human_readable_total_with_seconds: String,
    /// Unique ID of commit
    pub id: String,
    /// Author's description of this commit
    pub message: Option<String>,
    /// Reference, e.g., refs/heads/master
    pub r#ref: String,
    /// Time coded in editor for this commit in seconds
    pub total_seconds: f64,
    /// Truncated revision control hash of this commit
    pub truncated_hash: String,
    /// API URL with details about the current commit
    pub url: String,
}

/// Details of the project
#[derive(Deserialize, Debug)]
pub struct CommitProjectDetails {
    /// Unique ID of project
    pub id: String,
    /// Project name
    pub name: String,
    /// Project privacy setting
    pub privacy: Option<String>,
    /// Repository details
    pub repository: RepositoryDetails,
}

/// Details of the repository
#[derive(Deserialize, Debug)]
pub struct RepositoryDetails {
    /// Default branch if given for this repo
    pub default_branch: Option<String>,
    /// Remote repository description
    pub description: Option<String>,
    /// Number of repo forks if available
    pub fork_count: Option<usize>,
    /// Username and repo name, e.g., wakatime/wakadump
    pub full_name: String,
    /// Homepage of repository
    pub homepage: Option<String>,
    /// HTML URL for repository
    pub html_url: Option<String>,
    /// Unique ID of repository
    pub id: String,
    /// Whether this repo is a fork or original
    pub is_fork: bool,
    /// Whether this repo is private or public
    pub is_private: bool,
    /// Last time this repo was synced with remote provider in ISO 8601 format
    pub last_synced_at: Option<DateTime<Utc>>,
    /// Repository name
    pub name: String,
    /// Remote provider of repository, e.g., GitHub
    pub provider: String,
    /// Number of repo stars if available
    pub star_count: Option<usize>,
    /// API URL of remote repository
    pub url: String,
    /// Number of watchers of repo if available
    pub watch_count: Option<usize>,
}

impl Commit {
    #[cfg(feature = "blocking")]
    pub fn fetch(client: &WakapiClient, params: CommitParams) -> Result<Self, WakapiError> {
        let url = client.build_url(
            format!(
                "/api/v1/users/:user/projects/{}/commits/{}",
                &params.url.project, &params.url.hash
            )
            .as_str(),
            Some(serde_url_params::to_string(&params.params)?),
        );
        // // Debug, print url and response body
        // println!(
        //     "url: {}\nbody: {}",
        //     url,
        //     reqwest::blocking::Client::new()
        //         .get(&url)
        //         .header("Authorization", client.get_auth_header())
        //         .send()?
        //         .text()?
        // );

        let response = reqwest::blocking::Client::new()
            .get(&url)
            .header("Authorization", client.get_auth_header())
            .send()?;
        if response.status().is_success() {
            let body = response.json::<Commit>()?;
            Ok(body)
        } else {
            let error = response.json::<crate::error::ErrorMessage>()?;
            Err(WakapiError::ResponseError(error))
        }
    }

    #[cfg(not(feature = "blocking"))]
    pub async fn fetch(client: &WakapiClient, params: CommitParams) -> Result<Self, WakapiError> {
        let url = client.build_url(
            format!(
                "/api/v1/users/:user/projects/{}/commits/{}",
                &params.url.project, &params.url.hash
            )
            .as_str(),
            Some(serde_url_params::to_string(&params.params)?),
        );
        // // Debug, print url and response body
        // println!(
        //     "url: {}\nbody: {}",
        //     url,
        //     reqwest::Client::new()
        //         .get(&url)
        //         .header("Authorization", client.get_auth_header())
        //         .send()
        //         .await?
        //         .text()
        //         .await?
        // );

        let response = reqwest::Client::new()
            .get(&url)
            .header("Authorization", client.get_auth_header())
            .send()
            .await?;
        if response.status().is_success() {
            let body = response.json::<Commit>().await?;
            Ok(body)
        } else {
            let error = response.json::<crate::error::ErrorMessage>().await?;
            Err(WakapiError::ResponseError(error))
        }
    }
}