kiromi-ai-cli 0.2.2

Operator and developer CLI for the kiromi-ai-memory store: append, search, snapshot, regenerate, migrate-scheme, gc, audit-tail.
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! URI parsers for the slice-1 storage + metadata backends.

use std::path::PathBuf;

use kiromi_ai_memory::{InMemoryBackend, LocalFsBackend, MetadataStore, SqliteMetadata, Storage};

use crate::error::{CliError, ExitCode};

/// Parse a storage URI and return a boxed backend.
///
/// Recognised forms:
/// - `local:<path>` → `LocalFsBackend::new(path)`
/// - `memory:`      → `InMemoryBackend::new()`
pub(crate) async fn parse_storage(uri: &str) -> Result<Box<dyn Storage>, CliError> {
    if let Some(rest) = uri.strip_prefix("local:") {
        let p = PathBuf::from(rest);
        let s = LocalFsBackend::new(p).await.map_err(CliError::from)?;
        return Ok(Box::new(s));
    }
    if uri == "memory:" || uri == "memory" {
        return Ok(Box::new(InMemoryBackend::new()));
    }
    Err(CliError {
        kind: ExitCode::Config,
        source: anyhow::anyhow!("unrecognised storage URI {uri:?}"),
    })
}

/// Parse a metadata URI and return a boxed store.
///
/// Recognised forms:
/// - `sqlite:<path>`     → `SqliteMetadata::connect_file(path)`
/// - `sqlite::memory:`   → `SqliteMetadata::connect_memory()`
pub(crate) async fn parse_metadata(uri: &str) -> Result<Box<dyn MetadataStore>, CliError> {
    if uri == "sqlite::memory:" {
        let m = SqliteMetadata::connect_memory()
            .await
            .map_err(CliError::from)?;
        return Ok(Box::new(m));
    }
    if let Some(rest) = uri.strip_prefix("sqlite:") {
        let m = SqliteMetadata::connect_file(rest)
            .await
            .map_err(CliError::from)?;
        return Ok(Box::new(m));
    }
    Err(CliError {
        kind: ExitCode::Config,
        source: anyhow::anyhow!("unrecognised metadata URI {uri:?}"),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn parses_memory_storage() {
        let s = parse_storage("memory:").await.unwrap();
        assert!(!s.id().is_empty());
    }

    #[tokio::test]
    async fn parses_sqlite_in_memory() {
        let m = parse_metadata("sqlite::memory:").await.unwrap();
        assert!(!m.id().is_empty());
    }

    #[tokio::test]
    async fn rejects_unknown_storage_scheme() {
        let err = parse_storage("s3://bucket/key").await.unwrap_err();
        assert_eq!(err.kind, ExitCode::Config);
    }

    #[tokio::test]
    async fn rejects_unknown_metadata_scheme() {
        let err = parse_metadata("postgres://x").await.unwrap_err();
        assert_eq!(err.kind, ExitCode::Config);
    }
}