Skip to main content

aven_core/
labels.rs

1use crate::ids::WorkspaceId;
2use anyhow::{Result, bail};
3use sqlx::SqliteConnection;
4
5use crate::db::Database;
6use crate::matching::is_near;
7use crate::projects::normalize_key;
8
9impl Database {
10    pub async fn list_labels(
11        &self,
12        workspace_id: &WorkspaceId,
13        search: Option<&str>,
14    ) -> Result<Vec<String>> {
15        let mut conn = self.acquire().await?;
16        list_labels_in_workspace(&mut conn, workspace_id, search).await
17    }
18
19    pub async fn resolve_labels(
20        &self,
21        workspace_id: &WorkspaceId,
22        labels: &[String],
23    ) -> Result<Vec<String>> {
24        let mut conn = self.acquire().await?;
25        resolve_labels_in_workspace(&mut conn, workspace_id, labels).await
26    }
27}
28
29pub fn normalize_label(input: &str) -> String {
30    normalize_key(input)
31}
32
33async fn near_labels(
34    conn: &mut SqliteConnection,
35    workspace_id: &WorkspaceId,
36    input: &str,
37) -> Result<Vec<String>> {
38    let needle = normalize_label(input);
39    Ok(list_labels_in_workspace(conn, workspace_id, None)
40        .await?
41        .into_iter()
42        .filter(|label| is_near(&needle, label))
43        .collect())
44}
45
46pub(crate) async fn list_labels_in_workspace(
47    conn: &mut SqliteConnection,
48    workspace_id: &WorkspaceId,
49    search: Option<&str>,
50) -> Result<Vec<String>> {
51    let search = search.map(normalize_label);
52    let labels = sqlx::query_scalar::<_, String>(
53        "SELECT name FROM labels WHERE workspace_id = ? ORDER BY name",
54    )
55    .bind(workspace_id)
56    .fetch_all(&mut *conn)
57    .await?;
58    Ok(labels
59        .into_iter()
60        .filter(|label| {
61            search
62                .as_deref()
63                .is_none_or(|search| label.contains(search))
64        })
65        .collect())
66}
67
68pub(crate) async fn ensure_label_exists_in_workspace(
69    conn: &mut SqliteConnection,
70    workspace_id: &WorkspaceId,
71    label: &str,
72) -> Result<String> {
73    let label = normalize_label(label);
74    if sqlx::query_scalar::<_, i64>(
75        "SELECT count(*) FROM labels WHERE workspace_id = ? AND name = ?",
76    )
77    .bind(workspace_id)
78    .bind(&label)
79    .fetch_one(&mut *conn)
80    .await?
81        > 0
82    {
83        Ok(label)
84    } else {
85        let choices = near_labels(conn, workspace_id, &label).await?;
86        eprintln!("error unknown-label input={}", label);
87        for choice in choices {
88            eprintln!("choice {choice}");
89        }
90        eprintln!("hint \"create the label explicitly\"");
91        bail!("unknown label");
92    }
93}
94
95pub(crate) async fn resolve_labels_in_workspace(
96    conn: &mut SqliteConnection,
97    workspace_id: &WorkspaceId,
98    labels: &[String],
99) -> Result<Vec<String>> {
100    let mut resolved = Vec::with_capacity(labels.len());
101    for label in labels {
102        resolved.push(ensure_label_exists_in_workspace(conn, workspace_id, label).await?);
103    }
104    Ok(resolved)
105}