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:
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "consumer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
[workspace]
|
||||
[dependencies]
|
||||
sausage-plugin = { path = "../../sdk" }
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"AddParams": {
|
||||
"description": "Parameters for a new note.",
|
||||
"properties": {
|
||||
"text": {
|
||||
"description": "The complete note text, including any newlines.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Added": {
|
||||
"description": "The result after a note enters the list.",
|
||||
"properties": {
|
||||
"count": {
|
||||
"description": "The number of notes, including the new note.",
|
||||
"format": "uint",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"count"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Empty": {
|
||||
"description": "Empty parameters for methods without arguments.",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "notes",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"description": "Add a note and return the new count.",
|
||||
"name": "add",
|
||||
"paramStructure": "by-name",
|
||||
"params": [
|
||||
{
|
||||
"description": "The complete note text, including any newlines.",
|
||||
"name": "text",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The complete note text, including any newlines.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Added"
|
||||
}
|
||||
},
|
||||
"summary": "Add a note and return the new count.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/AddParams"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "List all notes, oldest first.",
|
||||
"name": "list",
|
||||
"paramStructure": "by-name",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"summary": "List all notes, oldest first.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/Empty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"openrpc": "1.2.6",
|
||||
"x-sausage-endpoints": {},
|
||||
"x-sausage-imports": {}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Empty": {
|
||||
"description": "Empty parameters for methods without arguments.",
|
||||
"type": "object"
|
||||
},
|
||||
"GetParams": {
|
||||
"description": "The name of a secret to read.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The unique name within this vault.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PutParams": {
|
||||
"description": "The name and value of a secret.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The unique name within this vault.",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"description": "The text to store until process exit.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "secrets",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"description": "Open the in-memory vault and publish its endpoint.",
|
||||
"name": "serve",
|
||||
"paramStructure": "by-name",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"summary": "Open the in-memory vault and publish its endpoint.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/Empty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"openrpc": "1.2.6",
|
||||
"x-sausage-endpoints": {
|
||||
"vault": {
|
||||
"methods": [
|
||||
{
|
||||
"description": "Store a secret and report its name to the caller.",
|
||||
"name": "put",
|
||||
"paramStructure": "by-name",
|
||||
"params": [
|
||||
{
|
||||
"description": "The unique name within this vault.",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The unique name within this vault.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "The text to store until process exit.",
|
||||
"name": "value",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The text to store until process exit.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"summary": "Store a secret and report its name to the caller.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/PutParams"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Read a secret, or return null when its name is absent.",
|
||||
"name": "get",
|
||||
"paramStructure": "by-name",
|
||||
"params": [
|
||||
{
|
||||
"description": "The unique name within this vault.",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The unique name within this vault.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"summary": "Read a secret, or return null when its name is absent.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/GetParams"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"x-sausage-imports": {}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Empty": {
|
||||
"description": "Empty parameters for methods without arguments.",
|
||||
"type": "object"
|
||||
},
|
||||
"StartParams": {
|
||||
"description": "The server configuration.",
|
||||
"properties": {
|
||||
"port": {
|
||||
"description": "The HTTP port on all network interfaces.",
|
||||
"format": "uint16",
|
||||
"maximum": 65535,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"port"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "www",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"description": "Start the HTTP server and return its process ID.",
|
||||
"name": "start",
|
||||
"paramStructure": "by-name",
|
||||
"params": [
|
||||
{
|
||||
"description": "The HTTP port on all network interfaces.",
|
||||
"name": "port",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The HTTP port on all network interfaces.",
|
||||
"format": "uint16",
|
||||
"maximum": 65535,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"summary": "Start the HTTP server and return its process ID.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/StartParams"
|
||||
}
|
||||
}
|
||||
],
|
||||
"openrpc": "1.2.6",
|
||||
"x-sausage-endpoints": {
|
||||
"control": {
|
||||
"methods": [
|
||||
{
|
||||
"description": "Count the HTTP requests this server answered.",
|
||||
"name": "stats",
|
||||
"paramStructure": "by-name",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"format": "uint64",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"summary": "Count the HTTP requests this server answered.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/Empty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "End this process after the response reaches the client.",
|
||||
"name": "stop",
|
||||
"paramStructure": "by-name",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"summary": "End this process after the response reaches the client.",
|
||||
"x-sausage-params": {
|
||||
"$ref": "#/components/schemas/Empty"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"x-sausage-grace": 5,
|
||||
"x-sausage-imports": {}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//! Use typed calls from discovered provider documents in `imports/`.
|
||||
//! Copy fresh documents from `/proc/core/plugins/<provider>/discover.json` after an interface changes.
|
||||
use sausage_plugin::{Result, rpc, schema};
|
||||
sausage_plugin::import!("notes");
|
||||
sausage_plugin::import!("www");
|
||||
sausage_plugin::import!("secrets");
|
||||
struct Consumer;
|
||||
/// The text and HTTP port for this demonstration.
|
||||
#[schema]
|
||||
struct DemoParams {
|
||||
/// The note text to append.
|
||||
text: String,
|
||||
/// The port for the detached HTTP server.
|
||||
port: u16,
|
||||
}
|
||||
#[rpc]
|
||||
impl Consumer {
|
||||
/// Add a note, start a server, and inspect its control endpoint.
|
||||
async fn demo(&self, p: DemoParams) -> Result<u64> {
|
||||
let added = notes::add(notes::AddParams { text: p.text }).await?;
|
||||
eprintln!("{} notes", added.count);
|
||||
let pid = www::start(www::StartParams { port: p.port })
|
||||
.detach()
|
||||
.await?;
|
||||
let control = www::control::connect(format!("/proc/{pid}/control")).await?;
|
||||
control.stats().await
|
||||
}
|
||||
/// Store and read a value through the boot service's fixed endpoint.
|
||||
async fn vault(&self) -> Result<Option<String>> {
|
||||
let vault = secrets::vault::connect("/run/secrets").await?;
|
||||
vault
|
||||
.put(secrets::PutParams {
|
||||
name: "example".into(),
|
||||
value: "hello".into(),
|
||||
})
|
||||
.on_notify(|name, params| eprintln!("{name}: {params}"))
|
||||
.await?;
|
||||
vault
|
||||
.get(secrets::GetParams {
|
||||
name: "example".into(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
sausage_plugin::run(Consumer)
|
||||
}
|
||||
Generated
-115
@@ -1,115 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "notes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sausage-actor",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sausage-actor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "notes"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk" }
|
||||
serde_json = "1"
|
||||
|
||||
[workspace]
|
||||
@@ -1,31 +0,0 @@
|
||||
//! Version 2 adds a note count and fixes the crash command.
|
||||
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();
|
||||
}
|
||||
|
||||
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(¬es);
|
||||
json!({"type": "added", "count": notes.len()})
|
||||
}
|
||||
"list" => json!({"type": "notes", "items": notes}),
|
||||
"count" => json!({"type": "count", "count": notes.len(), "version": 2}),
|
||||
"crash" => json!({"type": "not_crashing", "version": 2}),
|
||||
_ => json!({"type": "error", "reason": "unknown message"}),
|
||||
};
|
||||
let _ = reply(&message, response);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "version 2 counts notes and no longer crashes",
|
||||
"steps": [
|
||||
{"send": {"type": "add", "text": "test note"}, "expect": {"type": "added"}},
|
||||
{"send": {"type": "list"}, "expect": {"type": "notes"}},
|
||||
{"send": {"type": "count"}, "expect": {"type": "count", "version": 2}},
|
||||
{"send": {"type": "crash"}, "expect": {"type": "not_crashing"}}
|
||||
]
|
||||
}
|
||||
Generated
+564
-4
@@ -2,12 +2,252 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
@@ -18,8 +258,29 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
name = "notes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sausage-actor",
|
||||
"serde_json",
|
||||
"sausage-plugin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -41,11 +302,136 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sausage-actor"
|
||||
name = "ref-cast"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
|
||||
dependencies = [
|
||||
"ref-cast-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast-impl"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regress"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "158a764437582235e3501f683b93a0a6f8d825d04a789dbe5ed30b8799b8908a"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "sausage-interface"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"schemars 0.8.22",
|
||||
"serde_json",
|
||||
"syn 2.0.119",
|
||||
"typify",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sausage-plugin"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"inventory",
|
||||
"sausage-interface",
|
||||
"sausage-plugin-macros",
|
||||
"schemars 1.2.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sausage-plugin-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sausage-interface",
|
||||
"serde_json",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"schemars_derive 1.2.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals 0.29.1",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals 0.30.0",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -75,7 +461,29 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -91,6 +499,35 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_tokenstream"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c49585c52c01f13c5c2ebb333f14f6885d76daa768d8a037d28017ec538c69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
@@ -102,12 +539,135 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0b89f47309feaeb23c4509c15c9a04234f7deccef6f96c3bfe95319819a304"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7b026f540b148b81043c720889dbb942b08659aa8a43f624ac4f04dbfc1861"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regress",
|
||||
"schemars 0.8.22",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.119",
|
||||
"thiserror",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ed96c57f06ae0839416b986921a98f18b220da63bbb243a8570a00c8492183"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"schemars 0.8.22",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.119",
|
||||
"typify-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wstd"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29b52936db10a79bb724dadd1c2c3aac958e8229dcb1f1c7f2b7044ca9fc6a3a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-task",
|
||||
"bytes",
|
||||
"futures-lite",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slab",
|
||||
"wasip2",
|
||||
"wstd-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wstd-macro"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "153db9b65508bf6c2efe26617169840c03671ba5719a35868db7682a5261f7d4"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
name = "notes"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk" }
|
||||
serde_json = "1"
|
||||
|
||||
[workspace]
|
||||
[dependencies]
|
||||
sausage-plugin = { path = "../../sdk" }
|
||||
|
||||
+40
-27
@@ -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(¬es);
|
||||
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(¬es)?)?;
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"description": "add a note, then list the notes",
|
||||
"steps": [
|
||||
{"send": {"type": "add", "text": "test note"}, "expect": {"type": "added"}},
|
||||
{"send": {"type": "list"}, "expect": {"type": "notes"}}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "secrets"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
[workspace]
|
||||
[dependencies]
|
||||
sausage-plugin = { path = "../../sdk" }
|
||||
@@ -0,0 +1,46 @@
|
||||
//! Keep a process-local vault behind a fixed endpoint at `/run/secrets`.
|
||||
//! Start `secrets.serve` as a detached boot service. Its memory lasts until process exit.
|
||||
use sausage_plugin::{Ctx, Result, listen, rpc, schema};
|
||||
use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
|
||||
struct Secrets;
|
||||
#[rpc(endpoints(vault=Vault))]
|
||||
impl Secrets {
|
||||
/// Open the in-memory vault and publish its endpoint.
|
||||
async fn serve(&self) -> Result<()> {
|
||||
listen("/run/secrets", Rc::new(Vault::default())).await
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct Vault {
|
||||
entries: RefCell<BTreeMap<String, String>>,
|
||||
}
|
||||
/// The name and value of a secret.
|
||||
#[schema]
|
||||
struct PutParams {
|
||||
/// The unique name within this vault.
|
||||
name: String,
|
||||
/// The text to store until process exit.
|
||||
value: String,
|
||||
}
|
||||
/// The name of a secret to read.
|
||||
#[schema]
|
||||
struct GetParams {
|
||||
/// The unique name within this vault.
|
||||
name: String,
|
||||
}
|
||||
#[rpc]
|
||||
impl Vault {
|
||||
/// Store a secret and report its name to the caller.
|
||||
fn put(&self, ctx: &Ctx, p: PutParams) -> Result<()> {
|
||||
ctx.notify("stored", sausage_plugin::serde_json::json!({"name":p.name}))?;
|
||||
self.entries.borrow_mut().insert(p.name, p.value);
|
||||
Ok(())
|
||||
}
|
||||
/// Read a secret, or return null when its name is absent.
|
||||
fn get(&self, p: GetParams) -> Result<Option<String>> {
|
||||
Ok(self.entries.borrow().get(&p.name).cloned())
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
sausage_plugin::run(Secrets)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "www"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
[workspace]
|
||||
[dependencies]
|
||||
sausage-plugin = { path = "../../sdk" }
|
||||
wstd = "0.6"
|
||||
@@ -0,0 +1,75 @@
|
||||
//! Serve files from `/state/www` and expose a control endpoint at `/self/control`.
|
||||
use sausage_plugin::{Ctx, Result, background, listen, pid, rpc, schema, state_dir};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
use wstd::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
iter::AsyncIterator,
|
||||
net::TcpListener,
|
||||
};
|
||||
struct Www;
|
||||
/// The server configuration.
|
||||
#[schema]
|
||||
struct StartParams {
|
||||
/// The HTTP port on all network interfaces.
|
||||
port: u16,
|
||||
}
|
||||
#[rpc(endpoints(control=Control),grace_seconds=5)]
|
||||
impl Www {
|
||||
/// Start the HTTP server and return its process ID.
|
||||
async fn start(&self, p: StartParams) -> Result<String> {
|
||||
let tcp = TcpListener::bind(&format!("0.0.0.0:{}", p.port)).await?;
|
||||
let control = Rc::new(Control { hits: Cell::new(0) });
|
||||
listen("/self/control", control.clone()).await?;
|
||||
background(serve(tcp, control));
|
||||
Ok(pid())
|
||||
}
|
||||
}
|
||||
struct Control {
|
||||
hits: Cell<u64>,
|
||||
}
|
||||
#[rpc]
|
||||
impl Control {
|
||||
/// Count the HTTP requests this server answered.
|
||||
fn stats(&self) -> Result<u64> {
|
||||
Ok(self.hits.get())
|
||||
}
|
||||
/// End this process after the response reaches the client.
|
||||
fn stop(&self, ctx: &Ctx) -> Result<()> {
|
||||
ctx.exit_after_response();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn serve(tcp: TcpListener, control: Rc<Control>) {
|
||||
let mut incoming = tcp.incoming();
|
||||
while let Some(Ok(mut stream)) = incoming.next().await {
|
||||
let mut request = [0; 8192];
|
||||
let Ok(size) = stream.read(&mut request).await else {
|
||||
continue;
|
||||
};
|
||||
let line = String::from_utf8_lossy(&request[..size]);
|
||||
let path = line
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap_or("/")
|
||||
.trim_start_matches('/');
|
||||
let (status, body) = match std::fs::read(state_dir().join(if path.is_empty() {
|
||||
"index.html"
|
||||
} else {
|
||||
path
|
||||
})) {
|
||||
Ok(body) => ("200 OK", body),
|
||||
Err(_) => ("404 Not Found", b"Not found".to_vec()),
|
||||
};
|
||||
control.hits.set(control.hits.get() + 1);
|
||||
let header = format!(
|
||||
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
|
||||
body.len()
|
||||
);
|
||||
let _ = stream.write_all(header.as_bytes()).await;
|
||||
let _ = stream.write_all(&body).await;
|
||||
let _ = stream.flush().await;
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
sausage_plugin::run(Www)
|
||||
}
|
||||
Reference in New Issue
Block a user