use gitlab::{AsyncGitlab, GitlabBuilder};
use ironflow_core::error::OperationError;
use ironflow_core::operation::OperationContext;
use crate::operation::GitLabOp;
pub struct GitLab {
inner: AsyncGitlab,
}
impl GitLab {
pub async fn from_context(ctx: &OperationContext) -> Result<Self, OperationError> {
Self::from_context_with_host(ctx, "gitlab.com").await
}
pub async fn from_context_with_host(
ctx: &OperationContext,
host: &str,
) -> Result<Self, OperationError> {
let secret =
ctx.secrets()
.get("gitlab_token")
.await?
.ok_or_else(|| OperationError::Secret {
message: "gitlab_token secret not found".to_string(),
})?;
Self::new(&secret.value, host).await
}
pub async fn new(token: &str, host: &str) -> Result<Self, OperationError> {
let inner = GitlabBuilder::new(host, token)
.build_async()
.await
.map_err(|e| OperationError::Http {
status: None,
message: e.to_string(),
})?;
Ok(Self { inner })
}
pub fn client(&self) -> &AsyncGitlab {
&self.inner
}
pub fn op<E>(&self, endpoint: E) -> GitLabOp<E> {
GitLabOp::new(self.inner.clone(), endpoint)
}
}
impl std::fmt::Debug for GitLab {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GitLab")
.field("client", &"[AsyncGitlab]")
.finish()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use ironflow_core::operation::{NoopSecretResolver, OperationContext};
use super::*;
#[tokio::test]
#[ignore]
async fn new_builds_with_valid_host() {
let gitlab = GitLab::new("glpat-xxxx", "gitlab.com").await;
assert!(gitlab.is_ok());
}
#[tokio::test]
#[ignore]
async fn debug_does_not_leak_token() {
let gitlab = GitLab::new("super-secret", "gitlab.com").await.unwrap();
let debug = format!("{gitlab:?}");
assert!(!debug.contains("super-secret"));
}
#[tokio::test]
async fn from_context_fails_when_token_missing() {
let ctx = OperationContext::new(Arc::new(NoopSecretResolver));
let err = GitLab::from_context(&ctx).await.unwrap_err();
assert!(err.to_string().contains("gitlab_token"));
}
}