#![allow(dead_code)]
use super::{VCSClient, VCSProvider, BranchInfo, CommitInfo, LanguageStats, PullRequestInfo, RepositoryInfo};
use anyhow::Result;
use reqwest::Client;
use serde::Deserialize;
pub struct GitLabClient {
client: Client,
base_url: String,
token: String,
}
#[derive(Deserialize)]
struct GitLabProject {
id: u64,
name: String,
#[serde(rename = "path_with_namespace")]
full_name: String,
description: Option<String>,
#[serde(rename = "default_branch")]
default_branch: String,
#[serde(rename = "http_url_to_repo")]
url: String,
#[serde(rename = "visibility")]
is_private: bool,
#[serde(rename = "star_count")]
stars: u32,
#[serde(rename = "forks_count")]
forks: u32,
}
#[derive(Deserialize)]
struct GitLabBranch {
name: String,
#[serde(rename = "commit")]
commit_info: GitLabCommitRef,
#[serde(rename = "protected")]
is_protected: bool,
}
#[derive(Deserialize)]
struct GitLabCommitRef {
id: String,
}
#[derive(Deserialize)]
struct GitLabCommit {
id: String,
message: String,
author_name: String,
authored_date: String,
stats: Option<GitLabCommitStats>,
}
#[derive(Deserialize)]
struct GitLabCommitStats {
additions: i32,
deletions: i32,
}
#[derive(Deserialize)]
struct GitLabMR {
iid: u32,
title: String,
state: String,
author: GitLabUser,
created_at: String,
merged_at: Option<String>,
}
#[derive(Deserialize)]
struct GitLabUser {
username: String,
}
#[derive(Deserialize)]
struct GitLabLanguage {
name: String,
share: f64,
}
impl GitLabClient {
pub fn new(token: String, base_url: Option<String>) -> Self {
Self {
client: Client::new(),
base_url: base_url.unwrap_or_else(|| "https://gitlab.com".to_string()),
token,
}
}
async fn get(&self, endpoint: &str) -> Result<reqwest::Response> {
let url = format!("{}{}", self.base_url, endpoint);
Ok(self.client.get(&url)
.header("PRIVATE-TOKEN", &self.token)
.send().await?)
}
}
impl VCSClient for GitLabClient {
fn provider(&self) -> VCSProvider {
VCSProvider::GitLab
}
async fn list_repositories(&self) -> Result<Vec<RepositoryInfo>> {
let response = self.get("/api/v4/projects?membership=true&per_page=100").await?;
let projects: Vec<GitLabProject> = response.json().await?;
Ok(projects.into_iter().map(|p| RepositoryInfo {
name: p.name,
full_name: p.full_name,
description: p.description,
language: None,
stars: p.stars,
forks: p.forks,
is_private: p.is_private,
default_branch: p.default_branch,
url: p.url,
}).collect())
}
async fn get_repository(&self, name: &str) -> Result<RepositoryInfo> {
let encoded = urlencoding::encode(name);
let response = self.get(&format!("/api/v4/projects/{}", encoded)).await?;
let p: GitLabProject = response.json().await?;
Ok(RepositoryInfo {
name: p.name,
full_name: p.full_name,
description: p.description,
language: None,
stars: p.stars,
forks: p.forks,
is_private: p.is_private,
default_branch: p.default_branch,
url: p.url,
})
}
async fn list_branches(&self, repo: &str) -> Result<Vec<BranchInfo>> {
let encoded = urlencoding::encode(repo);
let response = self.get(&format!("/api/v4/projects/{}/repository/branches", encoded)).await?;
let branches: Vec<GitLabBranch> = response.json().await?;
Ok(branches.into_iter().map(|b| BranchInfo {
name: b.name,
sha: b.commit_info.id,
is_default: false,
}).collect())
}
async fn list_commits(&self, repo: &str, limit: usize) -> Result<Vec<CommitInfo>> {
let encoded = urlencoding::encode(repo);
let response = self.get(&format!("/api/v4/projects/{}/repository/commits?per_page={}", encoded, limit)).await?;
let commits: Vec<GitLabCommit> = response.json().await?;
Ok(commits.into_iter().map(|c| CommitInfo {
sha: c.id,
message: c.message,
author: c.author_name,
date: c.authored_date,
additions: c.stats.as_ref().map(|s| s.additions).unwrap_or(0),
deletions: c.stats.as_ref().map(|s| s.deletions).unwrap_or(0),
}).collect())
}
async fn get_pull_requests(&self, repo: &str) -> Result<Vec<PullRequestInfo>> {
let encoded = urlencoding::encode(repo);
let response = self.get(&format!("/api/v4/projects/{}/merge_requests", encoded)).await?;
let mrs: Vec<GitLabMR> = response.json().await?;
Ok(mrs.into_iter().map(|m| PullRequestInfo {
number: m.iid,
title: m.title,
state: m.state,
author: m.author.username,
created_at: m.created_at,
merged_at: m.merged_at,
}).collect())
}
async fn get_languages(&self, repo: &str) -> Result<Vec<LanguageStats>> {
let encoded = urlencoding::encode(repo);
let response = self.get(&format!("/api/v4/projects/{}/languages", encoded)).await?;
let langs: std::collections::HashMap<String, GitLabLanguage> = response.json().await?;
let _total: u64 = langs.values().map(|l| l.share as u64).sum();
Ok(langs.into_iter().map(|(name, l)| LanguageStats {
name,
bytes: l.share as u64,
percentage: l.share,
}).collect())
}
}