sausage-plugin part 2: import! with typed clients (sausage_interface::client_rs), TCP endpoints with rpc.endpoint, examples

Part 1 fixes: complete stdout writes, buffered stdin, Rust type names
in schemas, enum variant docs, -32603 for serde errors in handlers,
crate docs and modules. End-to-end check of a generated consumer through
a fake core.
This commit is contained in:
2026-10-23 00:35:45 +01:00
parent ae1cb4cc99
commit 8d3c163992
42 changed files with 3020 additions and 856 deletions
+40 -27
View File
@@ -1,30 +1,43 @@
//! Keep notes in `/data/notes.json` and publish them at `/public/notes.txt`.
use sausage_actor::{json, messages, reply};
const DATA: &str = "/data/notes.json";
fn save(notes: &[String]) {
std::fs::write(DATA, serde_json::to_string(notes).unwrap()).unwrap();
std::fs::write("/public/notes.txt", notes.join("\n") + "\n").unwrap();
//! Persistent notes. Each request reads the shared file instead of process memory.
use sausage_plugin::serde_json;
use sausage_plugin::{Result, lock, rpc, schema, state_dir, write_atomic};
struct Notes;
/// Parameters for a new note.
#[schema]
struct AddParams {
/// The complete note text, including any newlines.
text: String,
}
fn main() {
let mut notes: Vec<String> = std::fs::read_to_string(DATA)
.ok()
.and_then(|text| serde_json::from_str(&text).ok())
.unwrap_or_default();
for message in messages() {
let body = &message.body;
let response = match message.kind() {
"add" => {
notes.push(body["text"].as_str().unwrap_or_default().to_string());
save(&notes);
json!({"type": "added", "count": notes.len()})
}
"list" => json!({"type": "notes", "items": notes}),
"crash" => panic!("asked to crash"),
_ => json!({"type": "error", "reason": "unknown message"}),
};
let _ = reply(&message, response);
/// 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(&notes)?)?;
Ok(Added { count: notes.len() })
}
/// List all notes, oldest first.
fn list(&self) -> Result<Vec<String>> {
load()
}
}
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()),
}
}
fn main() {
sausage_plugin::run(Notes)
}