Skip to main content

jasper_plugin_sdk/
rt.rs

1//! ABI 运行时(spec §6):内存分配、u64 打包、JSON 信封。
2//!
3//! 插件作者通常不直接用本模块——`register!` 宏会生成
4//! `plugin_alloc` / `plugin_free` / `plugin_dispatch` 三个导出并委托到这里。
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// 插件侧错误(映射为信封里的 `error` 对象)。`code` 取值见 spec §6.4。
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct PluginError {
12    pub message: String,
13    pub code: String,
14}
15
16impl PluginError {
17    pub fn new(code: &str, message: impl Into<String>) -> Self {
18        Self { message: message.into(), code: code.into() }
19    }
20    pub fn forbidden(msg: impl Into<String>) -> Self {
21        Self::new("forbidden", msg)
22    }
23    pub fn not_found(msg: impl Into<String>) -> Self {
24        Self::new("not_found", msg)
25    }
26    pub fn invalid(msg: impl Into<String>) -> Self {
27        Self::new("invalid", msg)
28    }
29    pub fn internal(msg: impl Into<String>) -> Self {
30        Self::new("internal", msg)
31    }
32    pub fn unsupported(msg: impl Into<String>) -> Self {
33        Self::new("unsupported", msg)
34    }
35}
36
37/// 请求信封:`{ "method": string, "params": <json> }`(spec §6.4)。
38#[derive(Debug, Deserialize)]
39pub struct Req {
40    pub method: String,
41    #[serde(default)]
42    pub params: Value,
43}
44
45/// 响应信封:`{ ok, result } | { ok, error }`(spec §6.4)。
46#[derive(Debug, Serialize, Deserialize)]
47pub struct Resp {
48    pub ok: bool,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub result: Option<Value>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub error: Option<PluginError>,
53}
54
55impl Resp {
56    pub fn ok(result: Value) -> Self {
57        Self { ok: true, result: Some(result), error: None }
58    }
59    pub fn err(e: PluginError) -> Self {
60        Self { ok: false, result: None, error: Some(e) }
61    }
62    pub fn into_result(self) -> Result<Value, PluginError> {
63        if self.ok {
64            Ok(self.result.unwrap_or(Value::Null))
65        } else {
66            Err(self.error.unwrap_or_else(|| PluginError::internal("宿主返回错误但缺 error 对象")))
67        }
68    }
69}
70
71/// 打包 (ptr, len) 为 u64:`(ptr << 32) | len`(spec §6.2)。
72pub fn pack(ptr: u32, len: u32) -> u64 {
73    ((ptr as u64) << 32) | len as u64
74}
75
76/// 解包 u64 为 (ptr, len)。
77pub fn unpack(packed: u64) -> (u32, u32) {
78    ((packed >> 32) as u32, (packed & 0xffff_ffff) as u32)
79}
80
81/// 分配 `size` 字节(对齐 1)。size=0 返回 0,不分配。
82pub fn alloc(size: usize) -> usize {
83    if size == 0 {
84        return 0;
85    }
86    let layout = std::alloc::Layout::from_size_align(size, 1).expect("bad layout");
87    unsafe { std::alloc::alloc(layout) as usize }
88}
89
90/// 释放 `alloc` 分配的内存。ptr=0 或 size=0 为 no-op。
91pub fn free(ptr: usize, size: usize) {
92    if ptr == 0 || size == 0 {
93        return;
94    }
95    let layout = std::alloc::Layout::from_size_align(size, 1).expect("bad layout");
96    unsafe { std::alloc::dealloc(ptr as *mut u8, layout) }
97}
98
99/// `plugin_dispatch` 的通用实现:解析请求信封 → 调业务闭包 → 写回响应。
100/// 入参缓冲区归宿主所有(宿主 alloc、宿主 free);返回值缓冲区由本函数 alloc、宿主读完 free。
101///
102/// # Safety 说明
103/// `ptr/len` 必须指向本线性内存里一段有效的请求字节(由宿主经 `plugin_alloc` 写入)。
104pub fn dispatch(ptr: usize, len: usize, f: impl Fn(&str, Value) -> Result<Value, PluginError>) -> u64 {
105    let req_bytes = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
106    let resp = match serde_json::from_slice::<Req>(req_bytes) {
107        Ok(req) => match f(&req.method, req.params) {
108            Ok(result) => Resp::ok(result),
109            Err(e) => Resp::err(e),
110        },
111        Err(e) => Resp::err(PluginError::invalid(format!("请求 JSON 解析失败: {e}"))),
112    };
113    let out = serde_json::to_vec(&resp).unwrap_or_else(|_| {
114        br#"{"ok":false,"error":{"message":"response serialize failed","code":"internal"}}"#.to_vec()
115    });
116    write_out(&out)
117}
118
119/// 把一段字节写进新分配的插件内存并打包返回(宿主读完负责 plugin_free)。
120pub fn write_out(bytes: &[u8]) -> u64 {
121    let ptr = alloc(bytes.len());
122    unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, bytes.len()) };
123    pack(ptr as u32, bytes.len() as u32)
124}
125
126/// 插件 → 宿主调用(spec §6.3)。仅 wasm 环境可用;native 下直接报错(供单测桩用)。
127pub fn call_host(method: &str, params: Value) -> Result<Value, PluginError> {
128    let req = serde_json::to_vec(&serde_json::json!({ "method": method, "params": params }))
129        .map_err(|e| PluginError::internal(format!("host 请求序列化失败: {e}")))?;
130    let packed = raw_host_call(&req)?;
131    let (ptr, len) = unpack(packed);
132    let bytes = unsafe { std::slice::from_raw_parts(ptr as usize as *const u8, len as usize) }.to_vec();
133    // 响应缓冲区由宿主经 plugin_alloc 写入,插件读完释放(spec §6.3)
134    free(ptr as usize, len as usize);
135    let resp: Resp = serde_json::from_slice(&bytes)
136        .map_err(|e| PluginError::internal(format!("host 响应 JSON 解析失败: {e}")))?;
137    resp.into_result()
138}
139
140#[cfg(target_arch = "wasm32")]
141fn raw_host_call(req: &[u8]) -> Result<u64, PluginError> {
142    #[link(wasm_import_module = "joplin")]
143    extern "C" {
144        fn host_call(ptr: u32, len: u32) -> u64;
145    }
146    Ok(unsafe { host_call(req.as_ptr() as u32, req.len() as u32) })
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150fn raw_host_call(_req: &[u8]) -> Result<u64, PluginError> {
151    Err(PluginError::internal("host_call 仅在 wasm 环境可用"))
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use serde_json::json;
158
159    #[test]
160    fn pack_unpack_round_trip() {
161        let packed = pack(0xdead_beef, 42);
162        assert_eq!(unpack(packed), (0xdead_beef, 42));
163        assert_eq!(unpack(pack(0, 0)), (0, 0));
164        assert_eq!(unpack(pack(u32::MAX, u32::MAX)), (u32::MAX, u32::MAX));
165    }
166
167    #[test]
168    fn envelope_shapes() {
169        let ok = serde_json::to_value(Resp::ok(json!({"x": 1}))).unwrap();
170        assert_eq!(ok, json!({"ok": true, "result": {"x": 1}}));
171        let err = serde_json::to_value(Resp::err(PluginError::forbidden("nope"))).unwrap();
172        assert_eq!(err, json!({"ok": false, "error": {"message": "nope", "code": "forbidden"}}));
173    }
174
175    #[test]
176    fn req_params_default_null() {
177        let req: Req = serde_json::from_str(r#"{"method":"metadata"}"#).unwrap();
178        assert_eq!(req.method, "metadata");
179        assert!(req.params.is_null());
180    }
181
182    #[test]
183    fn into_result_maps_error_code() {
184        let resp: Resp = serde_json::from_str(
185            r#"{"ok":false,"error":{"message":"m","code":"not_found"}}"#,
186        )
187        .unwrap();
188        let err = resp.into_result().unwrap_err();
189        assert_eq!(err.code, "not_found");
190    }
191
192    #[test]
193    fn native_host_call_errors_cleanly() {
194        let err = call_host("log", json!({})).unwrap_err();
195        assert_eq!(err.code, "internal");
196    }
197}