nils-memo-cli 0.3.3

CLI crate for nils-memo-cli in the nils-cli workspace.
Documentation
use serde_json::json;

use crate::cli::OutputMode;
use crate::errors::AppError;
use crate::output::{emit_json_results_with_meta, format_item_id, parse_item_id, text};
use crate::storage::Storage;
use crate::storage::repository;

pub fn run(
    storage: &Storage,
    output_mode: OutputMode,
    limit: usize,
    cursor: Option<&str>,
) -> Result<(), AppError> {
    if limit == 0 {
        return Err(AppError::usage("--limit must be greater than 0"));
    }

    let cursor = if let Some(raw_cursor) = cursor {
        if raw_cursor.trim().is_empty() {
            return Err(AppError::usage("--cursor must be non-empty when provided"));
        }

        let cursor_item_id = parse_item_id(raw_cursor)
            .ok_or_else(|| AppError::invalid_cursor(raw_cursor).with_code("invalid-cursor"))?;
        Some(
            storage
                .with_connection(|conn| repository::lookup_fetch_cursor(conn, cursor_item_id))?
                .ok_or_else(|| AppError::invalid_cursor(raw_cursor))?,
        )
    } else {
        None
    };

    let mut rows = storage.with_connection(|conn| {
        repository::fetch_pending_page(conn, limit.saturating_add(1), cursor.as_ref())
    })?;
    let has_more = rows.len() > limit;
    if has_more {
        rows.truncate(limit);
    }
    let next_cursor = has_more.then(|| {
        rows.last()
            .map(|row| format_item_id(row.item_id))
            .unwrap_or_default()
    });

    if output_mode.is_json() {
        let results = rows
            .iter()
            .map(|row| {
                json!({
                    "item_id": format_item_id(row.item_id),
                    "created_at": row.created_at,
                    "source": row.source,
                    "text": row.text,
                    "state": row.state,
                    "content_type": row.content_type,
                    "validation_status": row.validation_status,
                })
            })
            .collect::<Vec<_>>();

        return emit_json_results_with_meta(
            "memo-cli.fetch.v1",
            "memo-cli fetch",
            results,
            Some(json!({
                "limit": limit,
                "returned": rows.len(),
                "next_cursor": next_cursor,
                "has_more": has_more
            })),
            None,
        );
    }

    text::print_fetch(&rows);
    if let Some(next_cursor) = next_cursor {
        eprintln!("next cursor: {next_cursor}");
    }

    Ok(())
}