Skip to main content

lepiter_core/
plugin.rs

1//! plugin sdk for external snippet renderers.
2
3use std::io::{self, BufRead, Write};
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// ipc request sent to an external snippet plugin.
9#[derive(Debug, Deserialize, Serialize)]
10pub struct PluginRequest {
11    /// snippet type (e.g. `wardleyMap`).
12    #[serde(rename = "type")]
13    pub typ: String,
14    /// raw snippet json.
15    pub snippet: Value,
16}
17
18/// ipc response returned by an external snippet plugin.
19#[derive(Debug, Serialize, Deserialize)]
20pub struct PluginResponse {
21    /// whether the render succeeded.
22    pub ok: bool,
23    /// rendered text lines.
24    pub lines: Vec<String>,
25    /// optional error message.
26    pub error: Option<String>,
27}
28
29impl PluginResponse {
30    /// successful response with rendered lines.
31    pub fn ok(lines: Vec<String>) -> Self {
32        Self {
33            ok: true,
34            lines,
35            error: None,
36        }
37    }
38
39    /// error response with message.
40    pub fn error(message: impl Into<String>) -> Self {
41        Self {
42            ok: false,
43            lines: Vec::new(),
44            error: Some(message.into()),
45        }
46    }
47}
48
49/// runs a newline-delimited json plugin loop.
50///
51/// the handler is invoked once per request. responses are serialized back to stdout.
52pub fn plugin_loop<F>(mut handler: F) -> io::Result<()>
53where
54    F: FnMut(PluginRequest) -> PluginResponse,
55{
56    let stdin = io::stdin();
57    let mut stdout = io::BufWriter::new(io::stdout());
58    for line in stdin.lock().lines() {
59        let line = match line {
60            Ok(line) => line,
61            Err(_) => break,
62        };
63        if line.trim().is_empty() {
64            continue;
65        }
66        let req: Result<PluginRequest, _> = serde_json::from_str(&line);
67        let resp = match req {
68            Ok(req) => handler(req),
69            Err(err) => PluginResponse::error(format!("invalid request: {err}")),
70        };
71        let json = serde_json::to_string(&resp).unwrap_or_else(|_| {
72            "{\"ok\":false,\"lines\":[],\"error\":\"encode failed\"}".to_string()
73        });
74        writeln!(stdout, "{json}")?;
75        stdout.flush()?;
76    }
77    Ok(())
78}