OXYGENHUB

Coverage / crates/spectra-core/src/projects.rs 100.0%

32 of 32 lines run, 7 of 14 functions · run #28 on main · run missed

1
//! Projects (spec §23.1): any kind of work a group of agents shares. A
2
//! project has a name, a description, and optionally a folder and a link;
3
//! Spectra records where the folder is and reads nothing inside it. The
4
//! owner links projects (`project.linked`: one depends on another), which
5
//! is what `to: "downstream"` follows.
6
7
use crate::event::Event;
8
use anyhow::{Context, Result};
9
use serde_json::Value as Json;
10
use sqlx::SqliteConnection;
11
use std::path::Path;
12
13
/// Kinds of event the owner signs about projects.
14
pub const KINDS: &[&str] = &["project.added", "project.linked", "project.unlinked"];
15
16
/// A project's name from a folder: its name, lower-cased, made valid.
1720×
pub fn default_name(path: &Path) -> Result<String> {
1820×
    let base = path.canonicalize()?.file_name().and_then(|n| n.to_str()).map(str::to_lowercase).context("the path has no name")?;
19121×
    Ok(base.chars().map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' }).collect())
2020×
}
21
22
/// Apply a project event to the tables.
2342737×
pub async fn project(conn: &mut SqliteConnection, e: &Event) -> sqlx::Result<()> {
2442737×
    let p = e.json();
2542737×
    let s = |k: &str| p.get(k).and_then(Json::as_str).map(String::from);
2642737×
    match e.kind.as_str() {
2742737×
        "project.added" => {
283554×
            let Some(name) = s("name") else { return Ok(()) };
2942×
            sqlx::query("INSERT OR REPLACE INTO projects (name, path, description, url, seq) VALUES (?, ?, ?, ?, ?)")
3042×
                .bind(&name)
3142×
                .bind(s("path").filter(|p| !p.is_empty()))
3242×
                .bind(s("description").unwrap_or_default())
3342×
                .bind(s("url"))
3442×
                .bind(e.seq as i64)
3542×
                .execute(&mut *conn)
3642×
                .await?;
37
        }
3839183×
        "project.linked" | "project.unlinked" => {
394×
            let (Some(project), Some(on)) = (s("project"), s("depends_on")) else { return Ok(()) };
404×
            let sql = if e.kind == "project.linked" {
412×
                "INSERT OR IGNORE INTO project_relations (project, depends_on) VALUES (?, ?)"
42
            } else {
432×
                "DELETE FROM project_relations WHERE project = ? AND depends_on = ?"
44
            };
454×
            sqlx::query(sql).bind(project).bind(on).execute(&mut *conn).await?;
46
        }
4739179×
        _ => {}
48
    }
4939225×
    Ok(())
5042737×
}