use anyhow::{Context, Result};
use serde_json::Value;
use std::time::Duration;
const CALL_TIMEOUT: Duration = Duration::from_secs(2);
const REMEMBER_ASYNC: &str = "memory.remember_async";
fn build_request_body(content: &str, palace: Option<&str>, tags: &[String]) -> Value {
let mut obj = serde_json::Map::new();
obj.insert("content".to_string(), Value::String(content.to_string()));
if let Some(p) = palace {
obj.insert("palace".to_string(), Value::String(p.to_string()));
}
if !tags.is_empty() {
obj.insert(
"tags".to_string(),
Value::Array(tags.iter().cloned().map(Value::String).collect()),
);
}
Value::Object(obj)
}
pub async fn handle_note(content: String, palace: Option<String>, tags: Vec<String>) -> Result<()> {
if content.trim().is_empty() {
eprintln!("trusty-memory note: 'content' must not be empty");
std::process::exit(2);
}
let params = build_request_body(&content, palace.as_deref(), &tags);
crate::client::call_with_timeout(REMEMBER_ASYNC, params, CALL_TIMEOUT)
.await
.context(
"trusty-memory note: the memory was NOT written. \
If the daemon is not running, start it with `trusty-memory start`.",
)?;
println!("Queued.");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn note_builds_expected_body() {
let b = build_request_body("hello", None, &[]);
assert_eq!(b["content"], "hello");
assert!(
b.get("palace").is_none(),
"palace must be omitted when None"
);
assert!(b.get("tags").is_none(), "tags must be omitted when empty");
let b = build_request_body(
"facts about quokkas",
Some("wildlife"),
&["marsupials".to_string(), "australia".to_string()],
);
assert_eq!(b["content"], "facts about quokkas");
assert_eq!(b["palace"], "wildlife");
let tags = b["tags"].as_array().expect("tags array");
assert_eq!(tags.len(), 2);
assert_eq!(tags[0], "marsupials");
assert_eq!(tags[1], "australia");
}
#[tokio::test]
async fn note_reports_a_dropped_write_as_a_failure() {
let _guard = crate::commands::env_test_lock().lock().await;
let tmp = tempfile::tempdir().expect("tempdir");
unsafe {
std::env::set_var(trusty_common::DATA_DIR_OVERRIDE_ENV, tmp.path());
}
let result = handle_note("a note nobody will store".to_string(), None, Vec::new()).await;
unsafe {
std::env::remove_var(trusty_common::DATA_DIR_OVERRIDE_ENV);
}
let err = result.expect_err("a write nothing accepted must not report success");
assert!(
format!("{err:#}").contains("NOT written"),
"the failure must say the memory was not stored: {err:#}"
);
}
}