#![allow(dead_code)]
use super::{VCSClient, VCSProvider, BranchInfo, CommitInfo, LanguageStats, PullRequestInfo, RepositoryInfo};
use anyhow::Result;
use reqwest::Client;
use serde::Deserialize;
pub struct BitbucketClient {
client: Client,
username: String,
app_password: String,
}
#[derive(Deserialize)]
struct BitbucketRepo {
slug: String,
full_name: String,
description: Option<String>,
#[serde(rename = "mainbranch")]
main_branch: Option<BitbucketBranchRef>,
#[serde(rename = "type")]
repo_type: String,
links: BitbucketLinks,
}
#[derive(Deserialize)]
struct BitbucketBranchRef {
name: String,
}
#[derive(Deserialize)]
struct BitbucketLinks {
html: BitbucketLink,
}
#[derive(Deserialize)]
struct BitbucketLink {
href: String,
}
#[derive(Deserialize)]
struct BitbucketBranch {
name: String,
target: BitbucketCommitRef,
}
#[derive(Deserialize)]
struct BitbucketCommitRef {
hash: String,
}
#[derive(Deserialize)]
struct BitbucketCommit {
hash: String,
message: String,
author: BitbucketAuthor,
date: String,
}
#[derive(Deserialize)]
struct BitbucketAuthor {
raw: String,
}
#[derive(Deserialize)]
struct BitbucketPR {
id: u32,
title: String,
state: String,
author: BitbucketAuthor,
created_on: String,
merged_on: Option<String>,
}
impl BitbucketClient {
pub fn new(username: String, app_password: String) -> Self {
Self {
client: Client::new(),
username,
app_password,
}
}
async fn get(&self, endpoint: &str) -> Result<reqwest::Response> {
let url = format!("https://api.bitbucket.org/2.0{}", endpoint);
Ok(self.client.get(&url)
.basic_auth(&self.username, Some(&self.app_password))
.send().await?)
}
}
impl VCSClient for BitbucketClient {
fn provider(&self) -> VCSProvider {
VCSProvider::Bitbucket
}
async fn list_repositories(&self) -> Result<Vec<RepositoryInfo>> {
let response = self.get(&format!("/repositories/{}", self.username)).await?;
let data: serde_json::Value = response.json().await?;
let repos: Vec<BitbucketRepo> = serde_json::from_value(data["values"].clone())?;
Ok(repos.into_iter().map(|r| RepositoryInfo {
name: r.slug,
full_name: r.full_name,
description: r.description,
language: None,
stars: 0,
forks: 0,
is_private: r.repo_type == "repository",
default_branch: r.main_branch.map(|b| b.name).unwrap_or_else(|| "main".to_string()),
url: r.links.html.href,
}).collect())
}
async fn get_repository(&self, name: &str) -> Result<RepositoryInfo> {
let response = self.get(&format!("/repositories/{}/{}", self.username, name)).await?;
let r: BitbucketRepo = response.json().await?;
Ok(RepositoryInfo {
name: r.slug,
full_name: r.full_name,
description: r.description,
language: None,
stars: 0,
forks: 0,
is_private: r.repo_type == "repository",
default_branch: r.main_branch.map(|b| b.name).unwrap_or_else(|| "main".to_string()),
url: r.links.html.href,
})
}
async fn list_branches(&self, repo: &str) -> Result<Vec<BranchInfo>> {
let response = self.get(&format!("/repositories/{}/{}/refs/branches", self.username, repo)).await?;
let data: serde_json::Value = response.json().await?;
let branches: Vec<BitbucketBranch> = serde_json::from_value(data["values"].clone())?;
Ok(branches.into_iter().map(|b| BranchInfo {
name: b.name,
sha: b.target.hash,
is_default: false,
}).collect())
}
async fn list_commits(&self, repo: &str, limit: usize) -> Result<Vec<CommitInfo>> {
let response = self.get(&format!("/repositories/{}/{}/commits?pagelen={}", self.username, repo, limit)).await?;
let data: serde_json::Value = response.json().await?;
let commits: Vec<BitbucketCommit> = serde_json::from_value(data["values"].clone())?;
Ok(commits.into_iter().map(|c| CommitInfo {
sha: c.hash,
message: c.message,
author: c.author.raw,
date: c.date,
additions: 0,
deletions: 0,
}).collect())
}
async fn get_pull_requests(&self, repo: &str) -> Result<Vec<PullRequestInfo>> {
let response = self.get(&format!("/repositories/{}/{}/pullrequests", self.username, repo)).await?;
let data: serde_json::Value = response.json().await?;
let prs: Vec<BitbucketPR> = serde_json::from_value(data["values"].clone())?;
Ok(prs.into_iter().map(|p| PullRequestInfo {
number: p.id,
title: p.title,
state: p.state,
author: p.author.raw,
created_at: p.created_on,
merged_at: p.merged_on,
}).collect())
}
async fn get_languages(&self, _repo: &str) -> Result<Vec<LanguageStats>> {
Ok(Vec::new())
}
}