1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[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#[derive(Debug, Deserialize)]
39pub struct Req {
40 pub method: String,
41 #[serde(default)]
42 pub params: Value,
43}
44
45#[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
71pub fn pack(ptr: u32, len: u32) -> u64 {
73 ((ptr as u64) << 32) | len as u64
74}
75
76pub fn unpack(packed: u64) -> (u32, u32) {
78 ((packed >> 32) as u32, (packed & 0xffff_ffff) as u32)
79}
80
81pub 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
90pub 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
99pub 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
119pub 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
126pub fn call_host(method: &str, params: Value) -> Result<Value, PluginError> {
129 #[cfg(all(not(target_arch = "wasm32"), feature = "native-host"))]
130 {
131 crate::native_host::call(method, params)
132 }
133 #[cfg(not(all(not(target_arch = "wasm32"), feature = "native-host")))]
134 {
135 let req = serde_json::to_vec(&serde_json::json!({ "method": method, "params": params }))
136 .map_err(|e| PluginError::internal(format!("host 请求序列化失败: {e}")))?;
137 let packed = raw_host_call(&req)?;
138 let (ptr, len) = unpack(packed);
139 let bytes =
140 unsafe { std::slice::from_raw_parts(ptr as usize as *const u8, len as usize) }.to_vec();
141 free(ptr as usize, len as usize);
143 let resp: Resp = serde_json::from_slice(&bytes)
144 .map_err(|e| PluginError::internal(format!("host 响应 JSON 解析失败: {e}")))?;
145 resp.into_result()
146 }
147}
148
149#[cfg(target_arch = "wasm32")]
150fn raw_host_call(req: &[u8]) -> Result<u64, PluginError> {
151 #[link(wasm_import_module = "joplin")]
152 extern "C" {
153 fn host_call(ptr: u32, len: u32) -> u64;
154 }
155 Ok(unsafe { host_call(req.as_ptr() as u32, req.len() as u32) })
156}
157
158#[cfg(not(target_arch = "wasm32"))]
159fn raw_host_call(_req: &[u8]) -> Result<u64, PluginError> {
160 Err(PluginError::internal("host_call 仅在 wasm 环境可用"))
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use serde_json::json;
167
168 #[test]
169 fn pack_unpack_round_trip() {
170 let packed = pack(0xdead_beef, 42);
171 assert_eq!(unpack(packed), (0xdead_beef, 42));
172 assert_eq!(unpack(pack(0, 0)), (0, 0));
173 assert_eq!(unpack(pack(u32::MAX, u32::MAX)), (u32::MAX, u32::MAX));
174 }
175
176 #[test]
177 fn envelope_shapes() {
178 let ok = serde_json::to_value(Resp::ok(json!({"x": 1}))).unwrap();
179 assert_eq!(ok, json!({"ok": true, "result": {"x": 1}}));
180 let err = serde_json::to_value(Resp::err(PluginError::forbidden("nope"))).unwrap();
181 assert_eq!(err, json!({"ok": false, "error": {"message": "nope", "code": "forbidden"}}));
182 }
183
184 #[test]
185 fn req_params_default_null() {
186 let req: Req = serde_json::from_str(r#"{"method":"metadata"}"#).unwrap();
187 assert_eq!(req.method, "metadata");
188 assert!(req.params.is_null());
189 }
190
191 #[test]
192 fn into_result_maps_error_code() {
193 let resp: Resp = serde_json::from_str(
194 r#"{"ok":false,"error":{"message":"m","code":"not_found"}}"#,
195 )
196 .unwrap();
197 let err = resp.into_result().unwrap_err();
198 assert_eq!(err.code, "not_found");
199 }
200
201 #[test]
202 #[cfg(not(feature = "native-host"))]
203 fn native_host_call_errors_cleanly() {
204 let err = call_host("log", json!({})).unwrap_err();
205 assert_eq!(err.code, "internal");
206 }
207}