use crate::prelude::*;
use crate::forge::RepoBranch;
use gitlab_crate as gitlab;
use gitlab::*;
use gitlab::api::Query as _;
#[derive(Debug)]
pub struct Lab {
host: String,
gl: gitlab::Gitlab,
cache_user: IdCacheData<Lab, UserId>,
cache_proj: IdCacheData<Lab, ProjectId>,
}
impl From<(Gitlab, String)> for Lab {
fn from((gl, host): (Gitlab, String)) -> Self { Lab {
gl, host,
cache_user: default(),
cache_proj: default(),
} }
}
impl Lab {
#[throws(FE)]
pub fn new(config: &forge::Config) -> Box<dyn Forge> {
let gl = gitlab::Gitlab::new(
config.host.clone(),
config.get_token()?.ok_or_else(
|| FE::TokenAlwaysRequired(Kind::GitLab)
)?.0,
).map_err(|e| FE::ClientCreationFailed(e.into()))?;
Box::new(Lab::from((gl, config.host.clone()))) as _
}
}
impl TryFrom<IssueMrStatus> for
gitlab::api::projects::merge_requests::MergeRequestState
{
type Error = FE;
#[throws(FE)]
fn try_from(st: IssueMrStatus) -> Self {
use IssueMrStatus as F;
use gitlab::api::projects::merge_requests::MergeRequestState as G;
match st {
F::Open => G::Opened,
F::Closed => G::Closed,
F::Merged => G::Merged,
F::Unrepresentable => throw!(FE::UnsupportedState(
RemoteObjectKind::MergeReq,
format!("{:?}", st),
)),
}
}
}
impl ForgeMethods for Lab {
fn clear_id_caches(&mut self) {
self.cache_user.clear();
self.cache_proj.clear();
}
fn host(&self) -> &str { &self.host }
fn kind(&self) -> Kind { Kind::GitLab }
#[throws(FE)]
fn request(&mut self, req: &Req) -> Resp {
let req_dbg = || format!("{:?}", req);
let e_build = |e:String|
FE::OperationBuildFailed(anyhow!(e).context(req_dbg()));
let e_query = |e|
FE::UncleassifiedOperationError(AE::new(e).context(req_dbg()));
let e_process = |e|
FE::ResultsProcessingFailed(AE::new(e).context(req_dbg()));
match req {
Req::MergeRequests(q) => {
let target_project: ProjectId =
self.name2id_required(&q.target_repo)?;
let target_project = target_project.value();
let d = if let Some(number) = &q.number {
let number: u64 = number.parse().map_err(
|e: ParseIntError| FE::InvalidIdSyntax(
RemoteObjectKind::MergeReq, number.into(), e.to_string()
))?;
let mut d = api::projects::merge_requests::MergeRequest::builder();
d.project(target_project);
d.merge_request(number);
let d = d.build().map_err(e_build)?;
debug!("MergeRequests query {:?}", &d);
let d: Option<gitlab::types::MergeRequest> =
d.query(&mut self.gl).map_err(e_query)?;
d.into_iter().collect_vec()
} else {
let mut d = api::projects::merge_requests::MergeRequests::builder();
d.project(target_project);
if let Some(author) = &q.author {
let user: UserId = self.name2id_required(author)?;
d.author(user.value());
}
if let Some(statuses) = &q.statuses {
match statuses.iter().take(2).collect_vec().as_slice() {
[] => return Resp::MergeRequests { mrs: vec![] },
&[&state] => { d.state(state.try_into()?); },
[_,_,..] => { },
}
}
if let Some(source_branch) = &q.source_branch {
d.source_branch(source_branch);
}
d.with_merge_status_recheck(true);
let d = d.build().map_err(e_build)?;
debug!("MergeRequests query {:?}", &d);
let d = api::paged(d, api::Pagination::All);
let d: Vec<gitlab::types::MergeRequest> =
d.query(&mut self.gl).map_err(e_query)?;
d
};
debug!("MergeRequests reply {:?}", &d);
let mrs = d.into_iter().map(|g| Ok::<_,FE>(
Resp_MergeRequest {
number: g.iid.value().to_string(),
author: g.author.username,
state: IssueMrState {
locked: match g.discussion_locked {
None | Some(false) => IssueMrLocked::Unlocked,
Some(true) => IssueMrLocked::Locked,
},
status: {
use IssueMrStatus as F;
if let Some(_) = g.merged_at { F::Merged }
else if let Some(_) = g.closed_at { F::Merged }
else { F::Open }
},
},
source: RepoBranch {
repo: self.id2name_required(g.source_project_id)?.to_string(),
branch: g.source_branch,
},
target: RepoBranch {
repo: self.id2name_required(g.target_project_id)?.to_string(),
branch: g.target_branch,
},
}
))
.filter_ok(filter_mergerequests(&q))
.collect::<Result<Vec<_>,_>>()
.map_err(e_process)?;
Resp::MergeRequests { mrs }
}
Req::CreateMergeRequest(Req_CreateMergeRequest {
title, description,
target: RepoBranch { repo: target_repo, branch: target_branch },
source: RepoBranch { repo: source_repo, branch: source_branch },
_non_exhaustive, }) => {
let mut d = api::projects::merge_requests::CreateMergeRequest
::builder();
let target_proj: ProjectId = self.name2id_required(&target_repo)?;
d.target_project_id(target_proj.value());
d.target_branch(target_branch);
let source_proj: ProjectId = self.name2id_required(&source_repo)?;
d.project(source_proj.value());
d.source_branch(source_branch);
d.title(title);
d.description(description);
let d = d.build().map_err(e_build)?;
debug!("CreateMergeRequest query {:?}", &d);
let d: gitlab::types::MergeRequest =
d.query(&mut self.gl).map_err(e_query)?;
Resp::CreateMergeRequest {
number: d.iid.value().to_string(),
}
}
q@ Req::_NonExhaustive() => panic!("bad request {:?}", q),
}
}
}
impl Lab {
#[throws(FE)]
fn idcache_some_lookup<BQ,Q,RR,PR,EC,O>(
&mut self,
query_what: &str,
error_context: EC,
build_query: BQ,
process_results: PR,
) -> O
where
BQ: FnOnce() -> Result<Q, String>,
Q: gitlab::api::Endpoint,
PR: FnOnce(RR) -> Result<O, anyhow::Error>,
EC: FnOnce() -> String,
RR: DeserializeOwned,
{
(||{
let raw_results: RR =
build_query()
.map_err(|s| anyhow!("build {} query: {}", query_what, s))?
.query(&mut self.gl)
.with_context(|| format!("perform {} query", query_what))?;
let output = process_results(raw_results)?;
Ok::<_,AE>(output)
})()
.with_context(error_context)
.map_err(FE::AncillaryOperationFailed)?
}
}
impl IdCache<UserId> for Lab {
const UNKNOWN: RemoteObjectKind = RemoteObjectKind::User;
#[throws(FE)]
fn name2id_lookup(&mut self, username: &str) -> Option<UserId> {
self.idcache_some_lookup(
"User",
|| format!("username {:?} lookup failed", username),
||{
gitlab::api::users::Users::builder()
.username(username)
.build()
},
|user: Vec<UserBasic>| Ok::<_,AE>({
match user.as_slice() {
[user] => Some(user.id),
[] => None,
_ => throw!(anyhow!("multiple users found!")),
}
}),
)?
}
#[throws(FE)]
fn id2name_lookup(&mut self, user: UserId) -> Option<String> {
self.idcache_some_lookup(
"User",
|| format!("userid {:?} lookup failed", user),
|| Ok(
gitlab::api::users::User::builder()
.user(user.value())
.build()?
),
|ub: Option<UserBasic>| Ok(
ub.map(|ub| ub.name)
),
)?
}
fn id_cache(&mut self) -> &mut IdCacheData<Self, UserId> {
&mut self.cache_user
}
}
impl IdCache<ProjectId> for Lab {
const UNKNOWN: RemoteObjectKind = RemoteObjectKind::Repo;
#[throws(FE)]
fn name2id_lookup(&mut self, repo: &str) -> Option<ProjectId> {
self.idcache_some_lookup(
"Project",
|| format!("repo (project) {:?} lookup failed", repo),
|| Ok(
gitlab::api::projects::Project::builder()
.project(repo)
.build()?
),
|proj: Option<Project>| Ok(
proj.map(|proj| proj.id)
),
)?
}
#[throws(FE)]
fn id2name_lookup(&mut self, id: ProjectId) -> Option<String> {
self.idcache_some_lookup(
"Project",
|| format!("repo (project) id {:?} lookup failed", id),
|| Ok(
gitlab::api::projects::Project::builder()
.project(id.value())
.build()?
),
|proj: Option<Project>| Ok(
proj.map(|proj| proj.path_with_namespace)
),
)?
}
fn id_cache(&mut self) -> &mut IdCacheData<Self, ProjectId> {
&mut self.cache_proj
}
}