aven-core 0.1.13

Core library for the Aven local-first task manager
Documentation
use crate::ids::WorkspaceId;
use anyhow::{Result, bail};
use sqlx::SqliteConnection;

use crate::db::Database;
use crate::matching::is_near;
use crate::projects::normalize_key;

impl Database {
    pub async fn list_labels(
        &self,
        workspace_id: &WorkspaceId,
        search: Option<&str>,
    ) -> Result<Vec<String>> {
        let mut conn = self.acquire().await?;
        list_labels_in_workspace(&mut conn, workspace_id, search).await
    }

    pub async fn resolve_labels(
        &self,
        workspace_id: &WorkspaceId,
        labels: &[String],
    ) -> Result<Vec<String>> {
        let mut conn = self.acquire().await?;
        resolve_labels_in_workspace(&mut conn, workspace_id, labels).await
    }
}

pub fn normalize_label(input: &str) -> String {
    normalize_key(input)
}

async fn near_labels(
    conn: &mut SqliteConnection,
    workspace_id: &WorkspaceId,
    input: &str,
) -> Result<Vec<String>> {
    let needle = normalize_label(input);
    Ok(list_labels_in_workspace(conn, workspace_id, None)
        .await?
        .into_iter()
        .filter(|label| is_near(&needle, label))
        .collect())
}

pub(crate) async fn list_labels_in_workspace(
    conn: &mut SqliteConnection,
    workspace_id: &WorkspaceId,
    search: Option<&str>,
) -> Result<Vec<String>> {
    let search = search.map(normalize_label);
    let labels = sqlx::query_scalar::<_, String>(
        "SELECT name FROM labels WHERE workspace_id = ? ORDER BY name",
    )
    .bind(workspace_id)
    .fetch_all(&mut *conn)
    .await?;
    Ok(labels
        .into_iter()
        .filter(|label| {
            search
                .as_deref()
                .is_none_or(|search| label.contains(search))
        })
        .collect())
}

pub(crate) async fn ensure_label_exists_in_workspace(
    conn: &mut SqliteConnection,
    workspace_id: &WorkspaceId,
    label: &str,
) -> Result<String> {
    let label = normalize_label(label);
    if sqlx::query_scalar::<_, i64>(
        "SELECT count(*) FROM labels WHERE workspace_id = ? AND name = ?",
    )
    .bind(workspace_id)
    .bind(&label)
    .fetch_one(&mut *conn)
    .await?
        > 0
    {
        Ok(label)
    } else {
        let choices = near_labels(conn, workspace_id, &label).await?;
        eprintln!("error unknown-label input={}", label);
        for choice in choices {
            eprintln!("choice {choice}");
        }
        eprintln!("hint \"create the label explicitly\"");
        bail!("unknown label");
    }
}

pub(crate) async fn resolve_labels_in_workspace(
    conn: &mut SqliteConnection,
    workspace_id: &WorkspaceId,
    labels: &[String],
) -> Result<Vec<String>> {
    let mut resolved = Vec::with_capacity(labels.len());
    for label in labels {
        resolved.push(ensure_label_exists_in_workspace(conn, workspace_id, label).await?);
    }
    Ok(resolved)
}