85 lines
2.5 KiB
Rust
85 lines
2.5 KiB
Rust
//! Persistent notes in `/state/notes/notes.json`.
|
|
//! Each call has its own process. The add method locks the file before its read-modify-write operation.
|
|
//! Atomic replacement lets readers observe a complete list without a lock.
|
|
use sausage_plugin::serde_json;
|
|
use sausage_plugin::{Result, lock, rpc, schema, state_dir, write_atomic};
|
|
/// The stateless notes plugin reads its file on each call.
|
|
struct Notes;
|
|
/// Parameters for a new note.
|
|
#[schema]
|
|
struct AddParams {
|
|
/// The complete note text, including any newlines.
|
|
text: String,
|
|
}
|
|
/// The result after a note enters the list.
|
|
#[schema]
|
|
struct Added {
|
|
/// The number of notes, including the new note.
|
|
count: usize,
|
|
}
|
|
#[rpc]
|
|
impl Notes {
|
|
/// Add a note and return the new count.
|
|
async fn add(&self, AddParams { text }: AddParams) -> Result<Added> {
|
|
let path = state_dir().join("notes.json");
|
|
let _guard = lock(&path).await?;
|
|
let mut notes = load()?;
|
|
notes.push(text);
|
|
std::fs::create_dir_all(state_dir())?;
|
|
write_atomic(path, serde_json::to_vec(¬es)?)?;
|
|
Ok(Added { count: notes.len() })
|
|
}
|
|
/// List all notes, oldest first.
|
|
fn list(&self) -> Result<Vec<String>> {
|
|
load()
|
|
}
|
|
}
|
|
/// Read the list, or return an empty list when the file does not exist.
|
|
fn load() -> Result<Vec<String>> {
|
|
match std::fs::read(state_dir().join("notes.json")) {
|
|
Ok(bytes) => Ok(serde_json::from_slice(&bytes)?),
|
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(vec![]),
|
|
Err(e) => Err(e.into()),
|
|
}
|
|
}
|
|
/// Start the SDK transport for notes.
|
|
fn main() {
|
|
sausage_plugin::run(Notes)
|
|
}
|
|
|
|
/// Tests run in fresh worlds under the WASI test runner.
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
/// The core lock protects two sequential additions and their counts.
|
|
#[sausage_plugin::test]
|
|
async fn add_appends_and_counts() {
|
|
assert_eq!(
|
|
Notes
|
|
.add(AddParams {
|
|
text: "milk".into()
|
|
})
|
|
.await
|
|
.unwrap()
|
|
.count,
|
|
1
|
|
);
|
|
assert_eq!(
|
|
Notes
|
|
.add(AddParams {
|
|
text: "eggs".into()
|
|
})
|
|
.await
|
|
.unwrap()
|
|
.count,
|
|
2
|
|
);
|
|
assert_eq!(Notes.list().unwrap(), ["milk", "eggs"]);
|
|
}
|
|
/// A fresh test world has no notes file.
|
|
#[test]
|
|
fn list_is_empty_without_a_file() {
|
|
assert!(Notes.list().unwrap().is_empty());
|
|
}
|
|
}
|