commit-lsp 0.2.0

Language Server for commit messages
use async_trait::async_trait;
use gitlab::api::{AsyncQuery, issues::IssueState};
use secure_string::SecureString;
use serde::Deserialize;
use tokio::sync::OnceCell;

use super::{IssueTrackerAdapter, Ticket, UpstreamError, builder::TrackerConfig};

pub struct Gitlab {
    client: OnceCell<gitlab::AsyncGitlab>,
    host: String,
    token: SecureString,
    project: String,
}

impl Gitlab {
    pub fn new(config: TrackerConfig) -> Option<Self> {
        let project = format!("{}/{}", config.url.owner?, config.url.name);
        Some(Self {
            client: Default::default(),
            host: config.url.host?,
            token: config.secret?,
            project,
        })
    }

    async fn client(&self) -> Result<&gitlab::AsyncGitlab, UpstreamError> {
        Ok(self
            .client
            .get_or_try_init(|| async {
                gitlab::GitlabBuilder::new(&self.host, self.token.unsecure())
                    .build_async()
                    .await
            })
            .await?)
    }
}

#[async_trait]
impl IssueTrackerAdapter for Gitlab {
    async fn list_ticket_numbers(&self) -> Result<Vec<u64>, UpstreamError> {
        let request = gitlab::api::issues::ProjectIssues::builder()
            .state(IssueState::Opened)
            .project(&self.project)
            .build()
            .expect("Failed to build request");

        let issues: Vec<Issue> = request.query_async(self.client().await?).await?;

        Ok(issues.into_iter().map(|i| i.iid).collect())
    }

    async fn get_ticket_details(&self, ids: &[u64]) -> Result<Vec<Ticket>, UpstreamError> {
        let request = gitlab::api::issues::ProjectIssues::builder()
            .iids(ids.iter().copied())
            .project(&self.project)
            .build()
            .expect("Failed to build request");

        let issues: Vec<Issue> = request.query_async(self.client().await?).await?;

        Ok(issues
            .into_iter()
            .map(|i| Ticket::new(i.iid, i.title, i.description))
            .collect())
    }
}

#[derive(Deserialize, Clone, Debug)]
struct Issue {
    iid: u64,
    title: String,
    description: String,
}